Format 是一种中等难度的 Linux 机器,它突出显示了由解决方案的结构方式引起的安全问题。立足点涉及PHP源代码审查,发现和利用本地文件读/写漏洞,并利用Nginx中的错误配置在Redis Unix套接字上执行命令。横向移动包括浏览 Redis 数据库以发现用户密码,而权限提升则围绕以 root 权限运行的 Python 脚本展开,该脚本容易受到代码注入的影响。
循例nmap
和我预想的一样,gitea包含了app子域的源码,毕竟遇到这种情况的次数也不少了
我们首先在app子域创建一个账号并登录
这里有一个最显眼的功能也是唯一一个功能就是创建新博客,我们可以指定子域域名,我们在源码里面定位到那个功能的代码
一开始我把目光放到了addsite函数上
addSite($_POST['new-blog-name']);
function addSite($site_name) {
if(isset($_SESSION['username'])) {
...
$tmp_dir = "/tmp/" . generateRandomString(7);
system("mkdir -m 0700 " . $tmp_dir);
system("cp -r /var/www/microblog-template/* " . $tmp_dir);
system("chmod 500 " . $tmp_dir);
system("chmod +w /var/www/microblog");
system("cp -rp " . $tmp_dir . " /var/www/microblog/" . $site_name);
...
我们应该可以尝试控制$_POST[‘new-blog-name’]来尝试执行系统命令,但是我发现后端对new-blog-name进行了严格的过滤,只允许26个字母,所以使我打消了这个念头
if (isset($_SESSION['username']) && isset($_POST['new-blog-name'])) {
if(!preg_match('/^[a-z]+$/', $_POST['new-blog-name']) || strlen($_POST['new-blog-name']) > 50) {
print_r("Invalid blog name");
我创建了一个test子域
在gitea中我看到一个sunny子域,我猜那个应该是由app子域创建的,所以我刚刚创建的test子域应该与sunny子域有同样的代码结构
在edit/index.php中,我发现了任意文件读写
//add header
if (isset($_POST['header']) && isset($_POST['id'])) {
chdir(getcwd() . "/../content");
$html = "{$_POST['header']}";
$post_file = fopen("{$_POST['id']}", "w");
fwrite($post_file, $html);
fclose($post_file);
$order_file = fopen("order.txt", "a");
fwrite($order_file, $_POST['id'] . "\n");
fclose($order_file);
header("Location: /edit?message=Section added!&status=success");
}
//add text
if (isset($_POST['txt']) && isset($_POST['id'])) {
chdir(getcwd() . "/../content");
$txt_nl = nl2br($_POST['txt']);
$html = "{$txt_nl}";
$post_file = fopen("{$_POST['id']}", "w");
fwrite($post_file, $html);
fclose($post_file);
$order_file = fopen("order.txt", "a");
fwrite($order_file, $_POST['id'] . "\n");
fclose($order_file);
header("Location: /edit?message=Section added!&status=success");
}
尝试读取passwd
我尝试着在web目录下写入webshell,但很不幸的是应该是没有权限和解析php
读/etc/nginx/sites-available/default
...
location ~ /static/(.*)/(.*) {
resolver 127.0.0.1;
proxy_pass http://$1.microbucket.htb/$2;
...
这篇文章讲述了nginx的proxy_pass支持将请求代理到本地unix套接字
现在我们可以控制$1,我们看到register.php设置的字段
通过nginx的proxy_pass来对redis进行操作
将自己的账户的pro字段设置为true
刷新一下
function provisionProUser() {
if(isPro() === "true") {
$blogName = trim(urldecode(getBlogName()));
system("chmod +w /var/www/microblog/" . $blogName);
system("chmod +w /var/www/microblog/" . $blogName . "/edit");
system("cp /var/www/pro-files/bulletproof.php /var/www/microblog/" . $blogName . "/edit/");
system("mkdir /var/www/microblog/" . $blogName . "/uploads && chmod 700 /var/www/microblog/" . $blogName . "/uploads");
system("chmod -w /var/www/microblog/" . $blogName . "/edit && chmod -w /var/www/microblog/" . $blogName);
}
return;
}
现在我们是pro,上面这些命令会使我们能够对uploads/目录写入文件,借此来getshell
然后通过rce来使用祖传python3 payload
在redis发现了一个系统用户名的key
读一下发现了他的密码
hgetall cooper.dooper
直接登ssh
sudo -l
这是一个python脚本
我们主要关注脆弱点
username = r.hget(args.provision, "username").decode()
firstlast = r.hget(args.provision, "first-name").decode() + r.hget(args.provision, "last-name").decode()
license_key = (prefix + username + "{license.license}" + firstlast).format(license=l)
这里的license_key由几个变量拼接进来然后format,应该存在模板注入,而username这些都是在redis读取的,我们可以控制redis来触发模板注入
secret = [line.strip() for line in open("/root/license/secret")][0]
secret_encoded = secret.encode()
salt = b'microblogsalt123'
kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),length=32,salt=salt,iterations=100000,backend=default_backend())
通过模板注入来获取secret,设置username
再次运行
root flag还在老地方