security development under django framework: the birth of pwnhub

Posted by tetley at 2020-03-04

In December last year, as a security technology competition platform (, pwnhub (fat Hubble) was officially launched. During the internal test, pwnhub platform attracted many partners in the technology circle. At that time, several masters who developed pwnhub were asked the most questions:

"Can I have a pwnhub invitation code?"

Pwnhub has continuously hosted 10 competitions since its launch for 4 months. Due to the high quality of platform topics, more and more people who are interested in network security technology gather in pwnhub to compete and communicate with each other, during which many powerful individuals and teams emerge. In this process, the platform is also gradually improving its functions, so that users can get a better experience.


Today we don't talk about achievements!

Don't talk about operations!

Don't talk about feelings!

Talk about dry goods!

It's believed that many of my companions will be curious:

How is pwnhub such a competition platform developed? On the safety level, what are the hidden pits and avoidance methods? How to eliminate the bug in the function of exchanging points for gifts?

Let's invite master pH, one of the developers of pwnhub platform, to talk about his real experience!

Write in the front: the length is long, recommend you to collect before reading.

Technical premise: Based on the Vue + djangoweb technology stack adopting spa asynchronous communication mode.

Wechat module security

One of the advantages of Django framework is its own user module. We can use a small amount of code to build the overall user logic, including user login, password retrieval, permission control, etc. However, as pwnhub determines to use wechat login when selecting the model, it is necessary to modify the user logic.

My approach is to cut off most logics of Django users' login at the front desk, including: registration, login, password retrieval, password modification, leaving only exit login, and then I made the code logic related to wechat login.

For any application related to wechat, to ensure the communication with wechat, the following four variables need to be noted:


1. Application ID appid2. Application key appsecret 3. Communication token token 4. Message encryption key encodingaeskey

(the first three must be configured; the last three must be kept secret, and the former can be made public.)

App ID and app key

Is the certificate of communication between our server and wechat server

For example, because pwnhub needs to scan and log in, I have to put forward this requirement to wechat server and get the QR code picture. Then, the precondition for wechat server to trust this request is that I need to bring access_token to access, and access_token is a temporary certificate (valid for 2 hours). The way to get this temporary certificate is to use appid and appsecret.

Appid is to tell wechat server "who I am", and appsecret is to prove that I am "this person". Wechat will issue me a "pass" with a validity of 2 hours after confirming the corresponding relationship between the two. In the future, you should bring this pass to the wechat server, that is, access_token. If it expires, you can get it again in the same way.

Communication token

It is the certificate for wechat server to access our server

Because HTTP protocol is a stateless protocol, although I have proved to wechat server who I am, I cannot establish a "communication channel", so when wechat server needs to access me, I need to verify his identity.

Token is equivalent to a signature key. Before wechat server accesses me, it will generate a random number and send it to my server after signing with the current timestamp and token using SHA1. I need to verify it and return an error if the signature is not correct.

Message encryption key

It is a way to ensure that communication information is not stolen by middlemen

Because WeChat official account server does not necessarily configure HTTPS, it is possible to use EncodingAESKey to encrypt message content in the communication process.

In general, the communication process between users, pwnhub and wechat server is as follows:

There are several problems in this process that are often ignored by developers:

The disclosure of appsecret and token is an old problem. Many developers upload their own code directly to the third-party code hosting platform such as GitHub, but they did not delete such sensitive information later. In another case, even if the user discovers and deletes the information in time, the features of GitHub can also enable the attacker to find the information in the submission record. Fortunately, both appsecret and token can be reset. Once a leak is found, the first step is to reset these two configurations.

Time stamp (timestamp) is not verified, and this problem exists in the WeChat official account code:

In this code, when the security signature is verified to be equal, it will be decrypted directly. Then, the timestamp has no meaning. The correct way is to check whether the difference between the current time and the time stamp is within a certain range while verifying that the signatures are equal.


Ensure that the communication between the server and the WeChat official account is not monitored by the third party.

If your server is not configured with an HTTPS certificate, you should configure the value correctly.

In addition, because WeChat official account is used to communicate with XML, it is inevitable that XML security related problems will be encountered. Although the third party users can not request the server without guaranteeing Token's disclosure, we should not trust any user's input even if the user is WeChat server.

XxE and other common security problems in XML will not be discussed here. I use the module defisedxml ( in Python to ensure the security of XML parsing.

API security

Because it is a front-end and back-end separation project, all the front-end parts of pwnhub use API communication. API security is the top priority of pwnhub's overall security. API communication can be regarded as the exchange of actions and data. Once there is an unconfigured part, it may lead to loopholes such as horizontal permission bypass and information disclosure.

Pwnhub backend is developed by Django rest framework (hereinafter referred to as DRF). DRF is an API extension library based on Django. Through its built-in serializer concept, we can easily control the "attributes I want users to see" and "attributes that users can control" in the model.

The role of serializer is often misunderstood, such as the assumption that the serializer of DRF will lead to deserialization related vulnerabilities. Although it has such a name that is easy to be misunderstood, the actual function of the serializer is similar to that of the form in the native Django. Its main function is to control the content entered by the user and verify it. But the difference is that the serializer can also control the range of output information.

We can understand serializer as a medium between model and view. View sends the information entered by users to serializer for filtering and verification. After the verification is successful, it is sent to model and saved in the database. Model takes the information out of the database and hands it to serializer for filtering, and only displays the content allowed by the developer, and hands it to view. Views displays it Here is the flow:

In the code, we define fields in the serializer to limit the properties that can be displayed or modified:

The above code is a simple serializer corresponding to the user model. It can be seen that I can define fields to list the fields required by the program, and then define read only fields to limit the read-only fields.

In other words, user ID, user name, email, user score, gold coin and registration time are not allowed to be modified.

But the above code has an obvious problem: although it restricts which fields are read-only, it does not restrict which fields are write only.

If you use DRF's built-in view, every time you create or update a model object, the modified serializer, that is, all the field values in fields, will be automatically displayed. But in fact, some fields are not suitable for display, such as password. This field should be defined as "only writable but not read":

This detail is easy to be ignored in development. If the design is not good, it is likely to lead to any user sensitive information disclosure vulnerability (cve-2017-0882) similar to that of gitlab in the previous few days.

However, the way to define write only and read only in DRF is really quite wonderful. Read only has a simple way to write read only fields, but write only does not.

Authority control

DRF has a highly customized authentication mechanism.

Because it is an API framework, the front-end may be a browser or a mobile app, so the cookie and session authorization methods in the regular web are not necessarily effective.

DRF has three built-in authentication methods:

Users can also define their own authorization methods and match them arbitrarily. For example, pwnhub now uses session for authorization. If you want to develop mobile app in the future, you may add token based authorization methods.

Through the authorization method, DRF marks each request with a user (the user who is not logged in is called anonymous user), but whether the user has access to an API requires permission.

In other words, the authentication module is only responsible for "issuing a pass" to the request, and the permission module is responsible for checking whether the "pass" has permission to access a certain location.

Different API views can choose to use different authentication and permission, which can be collocated according to their own access rights, or use the global default value. Pwnhub's foreground permission is very simple. It is authorized by session, and there are only two permissions: login user and anonymous user, so it is relatively simple to configure.

In addition, DRF provides a more advanced module: throttling. With this module, you can define how often a user accesses an API. For example, a video website allows each free user to watch 5 videos a day, which requires the following steps:

Of course, the function of pwnhub is not so complicated for the time being.

Front-end security

Front end security is a topic that cannot be bypassed. In recent years, the development mode of front-end and back-end separation to a certain extent reduces the front-end vulnerabilities such as XSS caused by the direct output of user input by back-end code, but at the same time, it also gives birth to a new attack mode: in the case that front-end and back-end are not completely separated, client side template injection may occur.

In this article, I will not introduce it more. For interested partners, please refer to this article )。

Here are four entry points to discuss the possible front-end vulnerabilities pwnhub may face:


XSS in Vue

Pwnhub front-end is based on Vue framework, and developers can easily display the data obtained from the back-end in the front-end through data binding. And this process is relatively safe: Vue recognizes data as plain text by default, rather than HTML code, when exporting data to a template.

But not all the output from all locations is suitable for plain text, and not all programmers can correctly understand the logic they need to write.

For example, if the product manager wants the user's comments to contain pictures and hyperlinks, the front-end page cannot transcode the comments when outputting them. This requires the use of the v-html directive in Vue:

See the above code. Comment.username is the name of the reviewer. It is wrapped in double braces and recognized as plain text by default. When comment.content is placed in the v-html instruction, it will be recognized as HTML code. Then, once a user submits a malicious HTML comment and the backend does not handle it, XSS vulnerability will be caused.

If you need to solve similar problems, you can use my Python XSS filter ( to process the comments in the back end.


XSS in Django

Due to the particularity of Python operation mode, that is, entering from a certain entry point uniformly, the suffix of the uploaded file is usually not too dead, as long as it does not cover the PY file of the program itself, there will be no big problem. Therefore, many operations of Django itself do not check the suffix by default. For example, we define a model, which contains an imagefield ():

Many people think that imagefield is the image field provided by Django. Django should have done a careful inspection by itself. But in fact, Django only checks whether the file content uploaded by the user is a picture, not whether the file suffix is a picture suffix.

However, server middleware and browser do not judge the MIME type of a file according to the file content, but according to the suffix. Therefore, I only need to upload a file whose content conforms to GIF format, and whose suffix is. HTML, to construct an XSS vulnerability.

Visit a.html and execute successfully:

Moreover, if the current server supports syntax such as SSI or PHP, this problem may even escalate to a server-side vulnerability.

The solution to this problem is to add validators to the imagefield field, such as photo = models. Imagefield ('picture '), validators = [check_image_extension], null = true, blank = true), and check_image_extension to check the file suffix.


CSRF vulnerability

CSRF vulnerability has a default check in Django: all post, put, patch and delete requests will check the CSRF token. However, Django checks the CSRF token by comparing the token in the cookie with the cookie in the form or HTTP header to avoid malicious forgery of the request.

This is somewhat different from the design concept of DRF: because the front end of DRF can be a browser or not, cookie is not a necessary value.

Therefore, DRF forces the closing of Django's native csrfcheck (csrf_exempt (view)) in apiview:

The CSRF check is put in session authentication. There is no CSRF check in token authentication and basic authentication. (of course, there is no need to check, because neither token nor 401 account password can be obtained by the attacker.)

If the authentication is defined by the developers themselves, CSRF authentication needs special attention.

In addition, DRF only checks the CSRF token of the logged in user, which is not a problem in most cases, because the non logged in user does not need to forge the request. However, there is a case where we need to manually add @ method ﹣ decorator (ensure ﹣ CSRF ﹣ cookie) to these methods to generate CSRF token and conduct manual check (if you use the login view of Django and turn on CSRF global check, there is no need for manual check).

Pwnhub will obtain the CSRF token in the cookie at the front end and attach it to all HTTP requests to ensure that the normal process will not be blocked.


Json Hijacking

The back-end API may contain some sensitive information, so how to ensure that the data is not stolen by others in the front-end?

In theory, the cross domain principle of browsers is enough to defend against such attacks. However, if the data returned by the backend is "mistakenly" considered as a legitimate JavaScript or CSS data by the browser, it may cause information disclosure attacks, which is also the principle of many cross domain vulnerabilities such as jsonp hijacking.

DRF provides a concept called renderer in the output layer, that is, the rendering method of output data. Usually in the development environment, for debugging convenience, I will use browsableapirenderer, which provides a web page to input and display data:

But if you need to use it with the front end, you need to use jsonrenderer. It will convert the data output by the serializer into JSON and return it to the front-end framework:

As shown in the figure above, although the data we need to transfer is an array: ["PWN", "reverse", "crypto", "misc", "Web"], pwnhub wraps the data one layer, so what is the help of this layer of wrapping to improve the security?

This involves the JSON hijacking vulnerability (we need to distinguish JSON hijacking from JSON hijacking). As we all know, JSON can be natively supported by JavaScript, so if we use the < script > tag to load our API return results, there will be some wonderful reactions:

2. If the JSON API returns an array, the browser will not throw an exception

Therefore, if I return the second case, the attacker may steal the data in the array by hijacking the JavaScript array object.

For the latest version of JSON hijacking, please refer to this article:

By default, the jsonresponse provided by Django checks whether the data passed in by the user is a dictionary, because only the dictionary corresponds to the JSON is an object, which is relatively safe:

As above, safe defaults to true, in which case data must be dict.

Shopping mall logic

At the beginning of March 2017, pwnhub launched a simple shopping mall function, that is, users participating in the competition can exchange their points in the competition for gifts.

The launch of new functions is bound to introduce new security threats. However, as pwnhub's Mall transactions are purely virtual currency logic and are not complex, in addition to the traditional horizontal permission loopholes, the most easily ignored loopholes in the mall logic are actually conditional competition.

Conditional competition vulnerabilities have occurred around us many times. The most recent one is the problem of over cash raised by users in the late February 2017 of the small ring ( P = 609).

There are two possible places in pwnhub mall where conditional competition vulnerabilities may occur:

Some students may not understand the logic of purchasing goods in a shopping mall. I drew a sketch with pwnhub as an example:

Therefore, the above logical attackers have two attack modes:

2. Different users send multiple purchase requests for the same goods at the same time, all of which pass the "judgment of the remaining quantity of goods", resulting in the purchase of a negative quantity of goods, which is equivalent to the purchase of goods without goods.

For example, user a owns 10 gold coins, and just buys commodity B worth 8 gold coins. However, user a opens 10 threads and sends a purchase request at the same time. In the case that the database has not been modified, all 10 requests will determine that the balance of a is enough to purchase B goods, and then deduct 8 gold coins and place an order successfully. At this time, the number of gold coins of user a is 10 - 8 * 10 = - 70, but all 10 orders have been successfully paid.

For similar attacks, most databases usually provide the function of locking a record. That is to say, when querying a record, the. Select ﹣ for ﹣ update() syntax is used in Django orm.

In addition, the places where the logic of the mall is prone to bug are:

The first problem basically doesn't exist, because pwnhub is front-end and back-end. The order can be completed in the front-end. As long as the back-end receives the "purchase (payment)" request, it will immediately deduct the payment.

Second and third, we can verify it in serializer:

The solution to the second problem is to add min? Value to the integerfield. The solution to the third problem is There is no commodity price in fields at all, because the commodity price is obtained from the price of the gift object.

On the eve of on-line

A lot of work has been done. If there is a little carelessness during deployment, the previous security work may have been done for nothing.

Deployment entry point

1. Security deployment of Django framework

2. Security deployment of Django rest framework framework

3. Web container security deployment


Security deployment of Django framework

Before the project goes online, we can check the possible security problems by executing. / check -- deploy command:

It can be seen that the above security problems exist in the projects generated by default, which need to be solved before deployment:

1. Whether to enable the HSTs header and force HTTPS access

2. Whether the secure ﹣ content ﹣ type ﹣ nosniff outputs the nosniff header to prevent type confusion class vulnerability 3. Whether the secure ﹣ browser ﹣ XSS ﹣ filter outputs the x-xss-protection header to force the browser to enable XSS filtering 4. Whether the secure ﹣ SSL ﹣ redirect forces HTTP requests to jump to https5. Whether the session ﹣ cookie ﹣ is set to secure (not allowed to be transmitted in HTTP) 6 Whether CSRF token cookie is set to secure (not allowed to be transmitted in HTTP) 7. Whether CSRF token cookie is set to http only8. Whether x frame options returns X-FRAME-OPTIONS: deny header to prevent it from being loaded as a frame by other pages 9. Whether debug turns on debugging mode

Among them, debug must be closed. Other options mainly depend on your security requirements. For example, if you do not force HTTPS to be used for your page, some of them are unnecessary to be set to prevent middleman hijacking.

In addition, it is better to change the background address before going online. If you don't need to use Django's own background, you can remove it in installed APUs. The secret key is regenerated, not the same as the development and test environment: OpenSSL Rand 10 - out. Secret.


DRF framework security deployment

As mentioned earlier, in the development environment, DRF usually uses browsableapirender, but after deployment to the production environment, it is better to remove this renderer and only keep the jsonrenderer we need:

Because the attacker can easily test and attack the form and return result output by browsableapirenderer, or the front-end security vulnerability (such as XSS) of DRF itself may affect you.

In addition, DRF comes with a user login view ( (adding login to the browser API). If you don't want the user to be blasted, please check your to see if the view is added.


Web container security deployment

This is the general topic. Just follow the normal reinforcement method of the server.

Because pwnhub is deployed in docker, we need to forward it with nginx in the front end. However, there are some methods in Django that need to obtain IP (Django has a configuration called internal IPS. If the guest IP is in this configuration item, it may enjoy some functions that are only available in the debug mode). I use the HTTP ﹣ x ﹣ forward ﹣ for header, then the header submitted by the user must be overwritten in nginx, otherwise arbitrary IP forgery vulnerability may be caused.

Behind a seemingly simple platform lies the great efforts of developers, ranging from the formulation of the whole platform framework logic to the control of the details of security coding. It is the security development based on wechat module security, API security, authority control and front-end security that can guarantee the platform from malicious attacks to the greatest extent, so that all users have no worries, participate in competitions, communicate with each other safely on pwnhub platform, and constantly verify their own strength!

How to register pwnhub?

To ensure the level of competitors, pwnhub has not yet implemented free registration. You can follow pwnhub Weibo and leave a message asking for the invitation code! Xiaobian can only help you here~~~