-
Notifications
You must be signed in to change notification settings - Fork 32
Security & Design decisions
Building a web application to run as hidden service requires higher security measures than usual web applications: You are exposed to various kinds of (powerful) attackers that operate outside legal jurisdiction.
For this reason, reusing known components (i.e. web frameworks or parts thereof) is not a good idea: With their big codebase, they show security vulnerabilites sooner or later.
For MultiVendorBitcoinMart, we avoided third-party frameworks and built the application from scratch, considering the following options:
- Dynamic languages like Ruby would be a good choice concerning productivity and language features, but show vulnerabilites on a regular basis
- Programming a web application in C from scratch could be a silver bullet, since we'd have control over every single bit of the application and wouldn't rely on third parties. But this has some disadvantages:
- Writing a web application framework in C would require a lot of work: It provides none of the mechanisms for web (HTTP library, server, templating language etc.) which must be written from scratch or reused (and then you lose the 'no external libraries' advantage)
- Buffer overflows are an additional security risk
- So we decided to use a stripped-down PHP-core instead of C:
- By using only the language core and needed extensions, we minimize the attack surface and risk for vulnerabilites in those external parts
- As a language born for the web, we get stable HTTP libraries, a template language and strong tooling support
- LAMP is a stable and thousand times security-proven way to run the code in production
- PHP has a big community which helps in the adoption of MultiVendorBitcoinMart as an open source project
The foundation of MultiVendorBitcoinMart is our tiny, secure MVC framework. It is heavily inspired by programming techniques found in cutting-edge web frameworks like Ruby on Rails/Symfony.
Core and entry point of the application is our App class:
- Opens the database connection
- Checks the URL parameters to a whitelist of routes (controller / action pairs)
- Dispatches to the controller, runs the action & handles errors
The controllers contain the business logic by using the Model and render a PHP template:
See Controller class for further documentation.
Each model subclass corresponds to a database table and contains methods to work in an OOP-manner with the database entries. Database access happens via PDO (Prepared Statements only) and only work with MySQL, since we use some custom functions (RAND, Hashing).
See Model class for further documentation.
Views are plain old PHP templates (we avoided using a template language to minimize the attack surface) that are rendered by the controller and receive a small set of variables.
For obvious security reasons, javascript is not used on most hidden services. The Tor Browser Bundle (an up-to-date Firefox with secure configuration and some extensions) thus disables javascript completely.
Developing for the modern web without javascript seems unhandy first, but for MultiVendorBitcoinMart with its limited function set it was not a huge drawback. Especially since we could develop for up-to-date Firefox builds (that support the latest HTML & CSS features) as target platform, because almost all users on hidden services use the Tor Browser Bundle.
To keep the market as user friendly as possible, we use the following strategy for each end user action:
- HTML5 validation on the client side (in Views), for example:
<input type="number"
step="any"
name="price"
value="<?= floatval($product->price) ?>"
required="true"
min="0.0"
placeholder="0.1"
title="Price in bitcoin">
- Server side validation that is very strict and discards each request that did not respect the HTML5 client side validation:
public function update() {
# check for existence & format of input params
$this->accessDeniedUnless(isset($this->post['code']) && is_string($this->post['code']));
$this->accessDeniedUnless(isset($this->post['name']) && is_string($this->post['name']) && mb_strlen($this->post['name']) >= 3);
$this->accessDeniedUnless(isset($this->post['description']) && is_string($this->post['description']) && mb_strlen($this->post['description']) >= 0);
$this->accessDeniedUnless(isset($this->post['price']) && is_string($this->post['price']) && is_numeric($this->post['price']) && $this->post['price'] >= 0.0);
$this->accessDeniedUnless(isset($this->post['tags']) && is_string($this->post['tags']));
....
Even when web application developpers respect most of the OWASP guidelines, they are often not so thoroughly when protecting the internal details of their applications. For example, primary keys are shown to the end user, which gives an attacker unnecessary informations about the internals (i.e. how old is a record).
For this reasons, we do not only protect sensitive information (like passwords hashed with BCRYPT, using the backport of the new PHP5.5+ password functions), but hide all internals to the end user.
Notable examples:
- Products are referenced by a unique code instead of their incrementing ID (needed for hidden products)
- Vendor pages are referenced by a hash of their username (so they can't influence it)
- All other primary keys or foreign keys are hidden (concattenated with a session secret and then hashed - this also serves as CSRF protection)
We integrated bitcoin payments using bitcoind
that offers two interfaces:
- the
blocknotify
hook ofbitcoind
runs MultiVendorBitcoinMart's CLI scriptblock-notify
, whenever a new bitcoin block was seen in the blockchain. MultiVendorBitcoinMart saves all transactions in this block in the database for later processal (Source). - This processal is done periodically via
crond
: Cron invokes MultiVendorBitcoinMart's CLI scriptrun
, whichs processes all new transactions (Source) and checks for:- a payment to a multisig address has been made (order is paid by buyer),
- a payment from the multisig address to the vendor has been made (buyer signed & broadcasted transaction = order is finished)
- The application uses the
json-rpc
interface ofbitcoind
to:- retrieve all transactions of a newly seen block
- create multisig addresses
- create transactions
- To validate BIP32 extended public keys and derive child keys, Bitwasp's bitcoin lib is used (since
bitcoind
does not offer that functionaliy).
One of the most delicate tasks when programming a secure web application are file uploads: It allows a user to supply files that are transferred on the webserver's filesystem. Implement this mechanism in a secure manner has many pitfalls.
MultiVendorBitcoinMart's image upload for products uses the ImageMagick
library to handle the uploaded files (not ImageMagick PHP extension, but execute the binaries directly) and consists of 4 steps (Source Code):
- File type whitelist using
identify
: Instead of looking at the file extension (easily fakeable) or the user-supplied MIME type (fakeable), we use ImageMagick'sidentify
command to guess the file type from the magic byte and allow only JPEGs, PNGs or GIFs. - Stripping: Image metadata (that can contain malicious content) or identity-revealing EXIF data is removed using ImageMagick's
mogrify -strip
command - Resize & Convert: The image is resized and converted to JPEG. This re-renders the picture, which removes eventually residing malicious content in the picture. Also, we can always set the MIME type to JPEG when delivering the picture to end users.
- BLOB in DB: The resulting JPEG is then saved in the database as BLOB. This way, we don't have to worry about the file being accidentally executed.
MultiVendorBitcoinMart uses captchas in two places:
- Registration (to prevent automatic generation of user accounts)
- Login (to disallow brute-force attacks on user accounts)
Since ImageMagick was already in place for the image upload, we reused it to generate the captchas.
How the captchas are generated (Source code):
- Generating a new random captcha code
- Positioning, skewing & rotating the letters randomly on the image
- Randomly distort the image (apply wave, implode & blur)
We are not experts in captcha generation - although above mechanisms protect it from basic OCR programs, we assume a skilled attacker could crack the captchas easily. For production use, it should be improved (at least some additional lines over the letters should be implemented, to avoid extracting the letters easily).
If we would generate a new captcha on every request, this would make MultiVendorBitcoinMart vulnerable to DoS attacks. To avoid using a cron that pre-generates captchas, we maintain of pool of captchas with a default size of 20:
- At any time, no more than 20 captchas are in the database
- On each request, either a new captcha is generated (if the pool is not full) or a random captcha from the pool is taken
- If the captcha is solved correctly, it will be deleted from the pool
- If the captcha is not solved correctly, it remains in the pool
Using this mechanisms, bots will be able to create 20 captchas at most. After that, they can't generate a new code (and thus create no load) until they solve one from the pool. Regular users won't notice anything (assuming they are able to solve the captchas).
MultiVendorBitcoinMart uses PGP public keys by integrating PHP's GnuPG bindings (slightly extended to be used concurrently).
It is used in the following places:
- Users must define a PGP public key in their profile prior to become a vendor
- To prove they own the private key of this public key, they have to sign a message with this key
- The PGP public key is shown on the vendor page
- The field 'shipping info' is automatically encrypted with the vendor's public key when ordering a product
PGP auth is not implemented, but could be done similar to the check used to prove the key possession (Source code).
Since bitcoin integration was finished before PGP integration, the authentication for the admin interface is done with signing a message with a bitcoin private key of an address (bitcoind
's signmessage
and verifymessage
commands) that was defined using set-admin
during installation.