Code auditing solution for the Sixth National Cybersecurity Competition

Source: Internet
Author: User
Tags codeigniter

Code auditing solution for the Sixth National Cybersecurity Competition

XDCTF is an information security competition for college students nationwide. It is jointly organized by the Information Security Association of xidian and the network defense training base. The aim is to enhance students' interest in network knowledge, increase students' enthusiasm for learning network technology, and cultivate students' innovative consciousness, collaboration spirit and ability to link theory with practice.

This CTF WEB2 is a big question, with a total of four flags representing: getting the source code, taking the foreground management, taking the background, and getshell.

Target Site:

Follow the prompts:

0 × 01 get source code

"The day of the rain

Shi Yu is a student in a school who loves php development on weekdays. Eleven seven days, the people all in the country are preparing for circle of friends cup travel Photography Contest, and the bitter rain is only in the dormitory to a evil organization to develop CMS--XDSEC-CMS.

I like to open source when the rain will use git to update the XDSEC-CMS source code, ready to wait for the development after the completion of the push to github.

The result was discovered by the leader and he had to order all the rm source code. Under the lewd of the leadership, the rain had to delete all the source code.

But smart kids, can you find the source code of Shi yujun and find the vulnerabilities in it ?"

It is known that the method for obtaining source code is related to git.

Port 9418 is not enabled. It is not a Git protocol. Visit found 403, directory may exist, there is git leak source code vulnerability.

Use lijiejie's GitHack tool to get source code:

Not all source codes can be obtained. Only one README. md and one. gitignore can be obtained.

Read README. md: "All source files are in git tag 1.0 ".

The "time rain" operation can be reversed:

git initgit add .git commitgit tag 1.0git rm –rf *echo “All source files are in git tag 1.0” > README.mdgit add .git commit

The real source code is in the commit of tag = 1.0. So how can we extract the 1.0 source code from the leaked. git directory?

This question includes the "principles" and "Tools ". Of course, let's start with the principle.

First download the file according to the git directory structure. This file is actually a "Link" of commit ".

This is a text file, that is, a sha1 commit id:

Then let's briefly talk about git object.

Git object is the object that saves git content and is saved in the objects directory under the. git directory. The first two letters of Id (sha1 encoded) are the directory names, and the last 38 letters are the file names.

So d16ecb17678b0297516962e2232080200ce7f2b3 this id represents the directory is

Request (all git objects are compressed by zlib, so I use the pipeline to input the py script for Simple decompression ):

This is also a text file pointing to a new id: 456ec92fa30e600fb256cc535a79e0c9206aec33, and some information.

I will request this id again:

You can see that a binary file is obtained.

A Brief Introduction to git object file structure:

In this step, we will only access "Tree object" and "Blob Object ".

This graph can represent the relationship between objects:

In fact, the d16ecb17678b0297516962e2232080200ce7f2b3 I obtained for the first time is the commit object (green). The tree object (blue) I just obtained is the blob Object (red ).

The specific file structure of the tree object is:


In fact, the binary content we see is sha1 encoding and only.

Tree objects are generally directories, while blob objects are generally specific files. The file structure of Blob objects is simpler:

Simply put:

"Blob [file size] \ x00 [file content]"

If you understand the file structure, you can parse it. Start with 456ec92fa30e600fb256cc535a79e0c9206aec33, follow up when encountering a tree object, and save it as a specific file when encountering a blob Object.

Finally, using my analysis just now, I wrote a script (gitcommit. py) to get all the source code:

As follows:

View index. php and obtain the first flag:

Of course, if you know the principle, OK. If you can use tools, why do you need to write your own code?

Let's talk about the "tool method ".

I have to mention the built-in git tools: git cat-file and git ls-tree.

In fact, git ls-tree is used to parse the git object whose type is "tree", while git cat-file is used to parse the git object whose type is "blob. We only need to place the object in this location, and then call git ls-tree [git-id.

Like this tool:

You can use a little modification to obtain the source code of tag = 1.0:

The tool I modified is provided (because the principle has been clearly stated, I will not talk about how to use and change the tool ):

Https:// win the front-end Administrator

Code audit officially started.

First, the code is actually complete. If you want to run it locally, you need to install all php dependencies with composer and php5.5.0 or later + linux environments. Set the Web directory to./front.

The source code has no SQL structure and you can access the SQL to download the SQL initialization file. (This address can be found at the front-end)

The code can be seen from the perspective of Codeigniter-based cms. The template library uses twig, the database uses mysql, and the session uses files.

Not to mention the vulnerabilities I have left. First, check the foreground (because you do not know the background address ):

/Xdsec_app/front_app/controllers/Auth. php 110 rows handle_resetpwd function,

public function handle_resetpwd()    {        if(empty($_GET["email"]) || empty($_GET["verify"])) {            $this->error("Bad request", site_url("auth/forgetpwd"));        }        $user = $this->user->get_user(I(""), "email");        if(I('get.verify') != $user['verify']) {            $this->error("Your verify code is error", site_url('auth/forgetpwd'));        }…

It mainly determines whether the passed $ _ GET ['verify '] is equal to the $ user ['verify'] in the database. In the database structure, verify is null by default.

We can see from the weak Php type comparison (double equal sign) that when we pass in $ _ GET ['verify '] as an empty string '','' = null, you can bypass the judgment here.

However, the first line of code uses empty ($ _ GET ['verify ']) to check whether it is null, so it still needs to be bypassed.

See the I function for getting the GET variable. The prototype of the I function is the I function in ThinkPHP. People familiar with ThinkPHP should know that the I function calls trim by default for processing.

Check the source code and find that the I function in Xdsec-cms will also process it. Therefore, we can pass in % 20 to bypass the empty () Judgment, and then get an empty string after I function processing. If it is null, true is returned.

You can reset any user password.

What should I do next when I mine and reset the vulnerability?

View the HTML source file on the page. You can see the copyright statement at meta, which contains a sensitive email:


We reset the users represented by this email directly:

If a data packet is submitted, the reset is successful. (The front-end has enabled csrf defense, so you need to bring the token. The CI token is saved in the cookie, so you can check it)

Use the Reset account password to log on to the

2nd flags are found in the user attachment:


In addition to the flag, the backend address is/th3r315adm1n. php.

But there is no background account password, so the next audit is required.

Some people say they do not know the Administrator's mailbox. I want to say that even if you repeat my social engineering and website, there will be at most six or seven mailboxes. You will try them one by one, this is a try.

Information collection during penetration is also very important. If the Administrator/developer mailbox cannot be found, subsequent penetration may be difficult.

Compared with this article mentioned similar vulnerabilities, this vulnerability is much simpler:, and this vulnerability is found in practice.

Therefore, focusing on practice is the first consideration. 0 × 03 win the password of the background Administrator Account

Get the background address, do not know the Administrator account and password. Some comrades think of social engineering and brute-force attacks. In fact, the vulnerability is still found. I also explained it in hint.

This step requires a deep dive into the core framework of Codeigniter.

View/xdsec_cms/core/Codeigniter. php and you can see the script execution process:

Core-> instantiate the controller (execute the construct _ construct)-> hook-> controller Main Function

Among them, the hook is a script hook, which means you can add other code in the middle of execution.

The specific code of the background hook is in/xdsec_app/admin_app/config/hooks. php.

$hook['post_controller_constructor'] = function(){    $self = & get_instance();    $self->load->library('session');    if(SELF == "admin.php" || config_item("index_page") == "admin.php") {        $self->error("Please rename admin filename 'admin.php' and config item 'index_page'", site_url());    }    $self->template_data["is_admin"] = $self->is_admin();    if(method_exists($self, "init")) {        call_user_func([$self, "init"]);    }}; $hook['post_controller'] = function(){    session_write_close();};

I wrote two hooks: post_controller_constructor and post_controller. Post_controller_constructor is executed before a specific method is executed after the Controller class is instantiated.

In addition, there is another point in the core code. if we implement the _ remap method, the _ remap method will also hook up the original controller method:

if ( ! class_exists($class, FALSE) OR $method[0] === '_' OR method_exists('CI_Controller', $method)){            $e404 = TRUE;}elseif (method_exists($class, '_remap')){            $params = array($method, array_slice($URI->rsegments, 2));            $method = '_remap';}

_ Remap is executed after $ hook ['Post _ controller_constructor ']. I defined an init method in $ hook ['Post _ controller_constructor, this method will be called if implemented in the controller.

I disguise the remap method as a hook function for modifying the method name. In fact, I added a before_handler method to it. If the Controller implements it, it will be called.

(These two methods are actually inspired by tornado, which has two methods in tornado .)

The code is in/xdsec_app/admin_app/core/Xdsec_Controller.php:

public function _remap($method, $params=[]){    $method = "handle_{$method}";    if (method_exists($this, $method)) {        if(method_exists($this, "before_handler")) {            call_user_func([$this, "before_handler"]);        }        $ret = call_user_func_array([$this, $method], $params);        if(method_exists($this, "after_handler")) {            call_user_func([$this, "after_handler"]);        }        return $ret;    } else {        show_404();    }}


Therefore, in conclusion, the script execution sequence is:

Core-> _ construct-> hook-> init-> before_hanlder (check permissions here)-> controller subject-> after_handler

I put the code for checking the background permissions in before_handler. The init method is intended to initialize some class variables.

However, if the developer mistakenly places the key code in the init method or the _ construct method, an excessive permission will occur. (Because the before_handler Method for checking permissions is not executed yet)

Return to the Controller code. /Xdsec_app/admin_app/controllers/Log. php contains the init function:

public function init()    {        $ip = I("post.ip/s") ? I("post.ip/s") : $this->input->ip_address();        $this->default_log = $this->query_log($ip);        $this->ip_address = $ip;}

Obviously, it contains the key logic $ this-> query_log ($ ip );

Follow up the query_log method:

protected function query_log($value, $key="ip")    {        $user_table = $this->db->dbprefix("admin");        $log_table = $this->db->dbprefix("adminlog");        switch($key) {            case "ip":            case "time":            case "log":                $table = $log_table;                break;            case "username":            case "aid":            default:                $table = $user_table;                break;        }        $query = $this->db->query("SELECT `{$user_table}`.`username`, `{$log_table}`.*                                    FROM `{$user_table}`, `{$log_table}`                                    WHERE `{$table}`.`{$key}`='{$value}'                                    ORDER BY `{$log_table}`.`time` DESC                                    LIMIT 20");        if($query) {            $ret = $query->result();        } else {            $ret = [];        }        return $ret;}

The background code is generally less secure than the front-end code, which is well reflected here. A large number of where statements in the background are directly concatenated strings. here we can see that $ value is spliced into SQL statements.

$ Value is $ ip, and $ ip can be from $ this-> input-> ip_address ().

Those who are familiar with CI may feel that there is no problem, but I have to replace the ip_address function of CI with my own one here:


function ip_address()    {        if (isset($_SERVER["HTTP_CLIENT_IP"])) {            $ip = $_SERVER["HTTP_CLIENT_IP"];        } elseif (isset($_SERVER["HTTP_X_FORWARDED_FOR"])) {            $ip = $_SERVER["HTTP_X_FORWARDED_FOR"];        } elseif (isset($_SERVER["REMOTE_ADDR"])) {            $ip = $_SERVER["REMOTE_ADDR"];        } else {            $ip = CI_Input::ip_address();        }        if(!preg_match("/(\d+)\.(\d+)\.(\d+)\.(\d+)/", $ip))            $ip = "";        return trim($ip);}


This function does not seem to be a problem. In fact, the last regular expression does not add the ^ $ delimiters, so it is essentially a virtual setting. You only need to use " 'union select ..." You can bypass it. (The inspiration here comes from the ThinkPHP framework injection I dug last year, and there is no first-and-last-time qualifier. For details, see my wooyun)

Therefore, combined with the above-mentioned excessive permission vulnerability that has not been checked by init, an SQL injection without background logon is formed.

However, because the function checks permissions after init, 302 is returned directly without logon, And the debug mode of the background database is disabled, an error cannot be reported.

Only time-based blind injection can be used here.

Write a blind injection script (xdseccms. py) to run the administrator password:

Run the password c983cff7bc504d350ede4758ab5a7b4b

Secret 5: decrypt and log on.

Log on to the background and find the third flag in the javascript field of Background File Management:

Here is the ctf technique.

For framework-based code auditing like mine, the author may modify the core code of the Framework (of course, I don't have it here, I am all normal hooks ). If you modify the core code of the framework, it is difficult to find the vulnerability, because there are usually many core code of the framework.

At this time, you should use tools such as diff to compare the normal framework with the ctf source code you downloaded, so that you can easily know what the author has modified. 0 × 04 backend GETSHELL

The last step is getshell.

In fact, getshell is not difficult, because there is a file management function in the background. After reading the source code, we can find that we can rename the file, but there are several difficulties ):

1. Only static files such as js, css, gif, jpg, and txt can be renamed.

2. The new file name has a blacklist and cannot be renamed to A. php format.

3. Obtain the mime type of the old file after finfo processing. The mime type corresponding to the new file name suffix must be the same.

Difficulty 1: Where do I have the right static files?

The background can download files, but can only download files from the, this website is static file cdn, content we cannot control. This is a confusing point, but it cannot be used.

The front-end user can upload txt files, but the files uploaded by the user will automatically follow a random string of 8 characters. We cannot directly obtain the real file name.

What should I do?

View the SQL structure. It can be seen that 'realname' varchar (128) NOT NULL, the file name realname can be up to 128 characters, and the linux system file name can be up to 255 characters.

Therefore, we can upload a file with a length greater than 128 or less than 255. If the file is uploaded successfully, an error is returned when the database is inserted. The actual file name is obtained:

Extension extension (extension is only the suffix of .txt ):

Difficulty 2: blacklist of new file names.

Similar to the second flag, the third parameter of the I function is a regular expression used to check whether the input data is valid.

Trim is performed only after detection. Therefore, we can pass in "xxx. php" and bypass the blacklist using spaces. This is a common WAF Bypass Method.

Difficulty 3: How are mime types equal?

Because the new file name suffix must be. php, the mime type corresponding to the new file name suffix is text/x-php.

The mime type of the old file needs finfo extension for detection. The finfo extension of Php uses the file content to guess the mime type of the file. We passed in the file aaaa... Aaa.txt, only the first few characters are

But there is still a small pitfall.

When uploading files at the front-end, the following judgment will be made:

 private function check_content($name)    {        if(isset($_FILES[$name]["tmp_name"])) {            $content = file_get_contents($_FILES[$name]["tmp_name"]);            if(strpos($content, "<?") === 0) {                return false;            }        }        return true;}

If the first two characters ="

What should I do?

In fact, the bypass method is also very simple, using the BOM in windows.

The file we uploaded can be a file with a "BOM Header". In this case, the three characters in the file header are \ xef \ xbb \ xbf, not

Finfo still judges the file as text/x-php, bypassing the third difficulty.

Therefore, rename the file and perform getshell.

The whole process: first, the front-end uploads the php webshell with the BOM header. The file name length is 128 ~ Before 255, the SQL statement reported an error and reported the actual file name. The background jumps to this file using ../, rename into A. php suffix, and uses % 20 to bypass blacklist detection.


The final result is as follows:

Access webshell to view 4th flags:

(Because I have set permissions, it is not actually a real webshell. The game ends here)

This code audit question is the most practical web Question. It's basically all about the pitfalls and tips encountered during actual practice and actual audits. I will integrate them in xdsec-cms for you. But we are disappointed that no one has made it in the end.

Maybe I thought about the audit a little simpler, but it was difficult to think about the first git, which caused the score distribution to be inappropriate. I am sorry here.

I still hope that you can sit down and read the code through this question. The code process is clear and there are no vulnerabilities that can be dug out.

I uploaded some of the tools I used to github:


Here is the Writeup for other questions in XDCTF2015.

XDCTF writeup link: password: imwg

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.