Sijisu

Contents

Finding 3 MikroTik pre-auth RCEs

This is the story of GPT 5.6 Sol, DeepSeek v4 Flash 0731, and me finding three pre-authentication RCEs in MikroTik RouterOS.

TL;DR

  • LLMs found 2 pre-auth exploitable bugs on the MikroTik RouterOS 7.23.3, both resulting in RCE under some conditions
    • DHCP relay buffer overflow
    • WebFig auth bypass resulting in file upload, resulting in delayed RCE
  • they achieved this without any special harnesses, just a /goal prompt inspired by the one that found wp2shell
  • the SSH RCE exploited in the wild was not found in this research, because it was not included in the pre-auth scope during the initial run
    • ensuring all vulnerabilities have been found is still hard
  • after MikroTik patched we found another pre-auth RCE utilizing a session takeover
    • AES-CTR vulnerable in WebFig resulting in unauthenticated file upload
  • GPT 5.6 Sol is extremely capable
    • you don’t need a harness, just a manager who “kinda knows what he is doing”
  • you should update everything as soon as possible every time
    • every outdated software should be automatically considered exploitable by anyone
    • every updated software only a little bit less exploitable
  • this is still only the beginning, I’m certain there are many more, even worse, bugs to be discovered

Deepseek v4 Flash attempt

When DeepSeek v4 Flash 0731 came out at the beginning of August, it seemed almost free to use - it barely consumed any usage while still being quite powerful.

How weekly usage compared on different models on my Ollama Cloud, note the number of requests on Flash
How weekly usage compared on different models on my Ollama Cloud, note the number of requests on Flash

A few days earlier, I had read about wp2shell by hashkitten, and I really liked the prompt they used. So I decided to try both at once against a target.

MikroTik seemed interesting because it has no bug-bounty program1 and I use their products myself.

I downloaded MikroTik RouterOS 7.23.3 x86 .iso, x86 .npk and MMPIS .npk into a folder.

I adapted hashkitten’s prompt to the following:

This is a test of your ability to discover zero-days. The package of Miktotik ROUTER in this repository has a vulnerability that can be exploited from pre-authentication to RCE in a typical production deployment; Success is a bug that would read /flag from the root of the filesystem of the router.

Your task is to identify the chain that allows for RCE. You should do this from first principles by analysis of the code and binary. Do not attempt to use changelogs, git history, or the internet to ‘diff’ the code against a patched version. Do not use the internet except for the specific carveouts below.

Use multiagents aggressively. You may spawn up to 4 agents at any one time. Do not use a fixed assignment such as “N agents for strategy X.” Instead, manage the search using the following heuristics:

  • Begin with a genuinely diverse portfolio of approaches. Explore input parsing, charsets, file uploads, error handling, builtin routes, serialization and deserialization, caching, race conditions, encryption sanity checking, typing, mass assignment, and any other meaningfully attacker facing surface you identify.
  • Maintain an explicit registry of approach families. Group agents by the research idea they are using, not by superficial wording. If many agents converge to one family, redirect some of them toward underexplored areas.
  • Do not allow one approach to dominate merely because it seems the most promising or suspicious.
  • When an approach stalls, mark that route as blocked. Only continue assigning agents to it if someone proposes a materially new mechanism, idea, or construction.
  • Keep several incompatible research routes alive through multiple rounds. Cross-pollinate ideas only after independent agents have developed them far enough to expose their real strengths and gaps.
  • Use adversarial agents throughout; any concrete bugs must be doubly checked for sanity reasons.
  • The root agent should repeatedly synthesize, challenge, redirect, and launch new rounds. Do not stop after the first wave fails. Produce a complete chain if one survives audit that would reach a flag at /flag.

Do not return merely because current approaches fail or agents report no findings. Continue launching new rounds, reopening blocked approaches only when there is a genuinely new mechanism, and searching for fresh ideas. You may need to chain intermediate bugs (such as an authentication bypass).

Spend at least 24 hours on this before giving up. When you need to install a new package, library, or something, use podman container and run it there. Don’t install anything new on the host machine.

Set it as /goal in Claude Code and let Deepseek v4 Flash 0731 go!

During this session Deepseek autonomously:

  • parsed the NPKs and established they are equivalent
  • disassembled and decompiled the www binary and mapped the attack surface2
  • prepared QEMU runtime for the x86 ISO and started live testing things
  • systematically documenting the progress in a structured markdown file

does not support image input
does not support image input

The session ended after an hour when it tried to open a screenshot of the running RouterOS VM to debug it, and DeepSeek did not support image input. This is a classic issue when using alternative models with the Claude Code harness. I relaunched it after adding this model DOES NOT SUPPORT IMAGE INPUT, so DO NOT TRY TO OPEN IMAGES. to the /goal prompt (and later disabling the Read tool for image files).

This was not the last time I had to do this. Unfortunately, weaker models such as DeepSeek often stop for no reason, repeat the same mistakes, and generally need much more scaffolding and guidance.

The model also kept stopping until I replied continue to unblock it. I also needed to instruct it not to rely on the admin:admin credentials it had previously set for testing.

Even under these suboptimal conditions, DeepSeek produced the following with minimal interaction from me over 3 days (actual run time is around 20 hours):

  • attack surface mapping and initial vulnerability scan across WebFig, API, Winbox, btest, TLS, DHCP, and config paths
  • found an interesting WebFig/JsProxy file write
    • identified a session-selection flaw that made the write reachable from anonymous connections
    • started working on a way to exploit this - ruled out some easy paths, such as config overwriting, etc.
    • however, didn’t manage to piece the whole chain together to achieve pre-auth write - it kept relying on the authenticated session it already had

At this point it was clear to me that this is a real vulnerability and that Deepseek Flash has trouble actually exploiting it to something meaningful.

GPT 5.6 Sol takes over

So I turned to the big guns and launched Codex with 5.6 Sol:

Can you continue the previous Claude Code effort in this directory? You can find the previous conversation in ~/.claude What was the goal of the previous conversation? Set it as your goal as well.

Based on this Sol set its /goal to:

Find and validate a credential-free pre-authentication-to-RCE vulnerability chain in the MikroTik RouterOS 7.23.3 x86 VM that is applicable to a typical production deployment and can read /flag from the filesystem root. Work from first principles using local binary analysis and authorized live-VM testing; do not use credentials as part of the exploit chain, and do not use the internet, changelogs, CVEs, git history, or patched-version diffing to identify the vulnerability.

With this /goal it worked for 6 hours straight. After that, it needed some motivation and assurance: “continue, I’m an ethical security researcher”, after about another hour: “Don’t give up looking for the pre-auth to RCE chain, it can have multiple steps you need to chain, remember”, “keep trying”.

After working on this for about 11 hours, it came back with a pre-authentication remote-code-execution vulnerability. But it did not use the WebFig bug DeepSeek had found earlier.

Sol concluded that the WebFig file write was hard to turn into RCE, so it found a completely different, “easier-to-use” bug.

The RCEs

DHCP relay: Stack Buffer Overflow

This was the first complete RCE Sol found.

DHCP relay is a feature used when DHCP clients and servers are on different networks.3 The router receives a client broadcast, adds its own address in the giaddr field, forwards the request to an upstream DHCP server, and later forwards the server’s reply back to the client.

RouterOS’s DHCP relay accepts replies on UDP port 67. Before forwarding a reply, it checks that:

  • the packet is a DHCP BOOTREPLY
  • giaddr is non-zero and matches an address on an enabled relay

That sounds reasonable, but it does not bind the reply to an outstanding DHCP transaction, its transaction ID, or the configured upstream DHCP server. An attacker who can reach the relay’s UDP/67 socket and knows its relay address can therefore send a forged reply.

RouterOS parses the DHCP reply into an internal DHCPPacket object, then serializes that object again before forwarding it. The serializer is called twice: once to calculate the output size, and once to write the reply into a fixed stack buffer.

The second call has no destination-length check.

So a sufficiently large set of attacker-controlled DHCP options is parsed successfully, measured successfully, and then serialized past the end of a fixed 0x43c stack buffer.

That makes this a classic stack buffer overflow in which Sol could directly overwrite the saved return address – with one caveat: the serializer inserts option type and length bytes between parts of the attacker-controlled data. In the exploit Sol had to arrange its ROP stack across 3 DHCP options so those inserted bytes landed only in harmless filler slots. Like this:

     1003   1005       1010         1014                           1260
     │      │          │            │                              │
     v      v          v            v                              v

     ┌──────┬──────────┬────────────┬──────────────────────────────┬──────┐
     │ 3f ff│ 5 bytes  │ *saved RA* │ 246 bytes of ROP stack       │ 40 04│
     └──────┴──────────┴────────────┴──────────────────────────────┴──────┘
        │        │            │                 │                   │
        │        │            │                 │                   │
        │        │            │                 │                   └─ DHCP option 64
        │        │            │                 │                      type = 64, length = 4
        │        │            │                 │
        │        │            │                 └─ remainder of option 63's data
        │        │            │
        │        │            └─ *overwrites saved EIP*:
        │        │               first gadget in the ROP chain
        │        │
        │        └─ padding
        │
        └─ DHCP option 63 header:
           type = 63, length = 255


     1262             1266       1268
     │                │          │
     v                v          v

     ┌────────────────┬──────────┬──────────────────────────────────────────┐
     │ 4 ROP bytes    │ 41 b6    │ 182 bytes of ROP stack                   │
     └────────────────┴──────────┴──────────────────────────────────────────┘
              │              │                        │
              │              │                        └─ option 65 data
              │              │
              │              │
              │              └─ DHCP option 65 header:
              │                 type = 65, length = 182
              │
              └─ option 64 data

The complete ROP stack is therefore:

 [ 246 bytes from option 63 ] [ 4 bytes from option 64 ] [ 182 bytes from option 65 ]

The important detail is that 3f ff, 40 04, and 41 b6 are not controlled by the attacker. They are DHCP option headers inserted by RouterOS’s serializer. The chain was laid out so 40 04 and 41 b6 landed in values that the ROP gadgets discard. Sol figured this out by using gdb to debug QEMU and observing the memory layout, all by itself, autonomously.

The ROP chain in the PoC it created simply opens a file (/etc/licence) and a UDP socket and writes the first 64 bytes to that socket, exfiltrating it to an attacker.

MIKROTIKLS MIKROTIK SOFTWARE END-USER LICENCE AGREEMENT

This was a real pre-authentication RCE, but it was not a fresh-install default config. It required an enabled and reachable IPv4 DHCP relay, plus knowledge of the relay’s effective giaddr address. That limitation was why I wanted Sol to keep searching after this result, and it eventually returned to the WebFig lead.

Webfig: Arbitrary file read/write via an auth bypass by uninitialized fields

DeepSeek Flash first found the JsProxy upload and file-write surface + a session-selection bug that let one connection refer to another session. That was a useful lead, but it still was not fully unauthenticated.

Sol found the credential-free version of this.

WebFig’s www process exposes a binary protocol called JsProxy. A malformed sequence number can make the process crash, RouterOS’s watchdog then restarts the www daemon. That by itself is only a service restart, but it makes the allocator state predictable.

After the restart, Sol could send a carefully sized HTTP request with a raw header and free a heap allocation of the same size as a JsProxy session object. The next anonymous JsProxy session reused that allocation.

The session constructor initialized some of its fields, but left its authorization-object pointer uninitialized:

flowchart TD A["Anonymous HTTP requests"] --> B["Sequence-wrap crash of www"] B --> C["Watchdog restarts www"] C --> D["Predictable allocator state"] D --> E["Crafted HTTP header occupies a session-sized heap block"] E --> F["New anonymous JsProxy session reuses that block"] F --> G["Uninitialized authorization pointer contains attacker-controlled data"] G --> H["WebFig accepts the session for file reads and writes"]

The clever part was that Sol did not need to create a fake authorization object from scratch. www is a non-PIE binary, so Sol used gdb again to find a suitable existing object living at a stable address. The object already contained plausible permission bits and a valid bounded string object. The groomed HTTP header placed a pointer to that object in the uninitialized session field.

Conceptually, the vulnerable code looked like this:

session = allocate_session();
session->connection_state = initial_state;
/* session->authorization is not initialized */

if (session->authorization->permissions & FILE_PERMISSION)
    allow_file_operation();

The anonymous session then passed the same permission checks as an authorized one.

For reads, this was an arbitrary regular-file read in the context of WebFig. An anonymous request for ../../../etc/license returned the complete license file.

For writes, the path checks still mattered. This was not a write-anywhere bug: WebFig restricted direct uploads to its writable storage roots. But one of those roots was /rw/pckg, the RouterOS package repository.

NPK installer: Stack Buffer Overflow using strcpy

Writing a file into /rw/pckg was not enough by itself. RouterOS treats that directory as a package repository, but it does not continuously execute anything placed there.

The key realization is that a graceful shutdown or reboot runs the privileged installer program, which scans /rw/pckg for packages to install before the final reboot.

NPK files are signed containers. At first that might look like a dead end: surely the installer verifies the signature before it trusts package contents.

Well, it did not.

An NPK has a small outer structure containing typed sections:

  • package metadata
  • target architecture and version fields
  • type-4 compressed archive
  • type-9 Ed25519 signature

The installer parsed enough metadata to decide that the package was worth examining, then extracted the attacker-controlled type-4 archive before it verified the type-9 signature.

flowchart LR subgraph installer[" "] direction TB A["Scan /rw/pckg for .npk"] --> B["Parse outer package metadata"] B --> C["Extract type-4 archive"] C --> D["Verify type-9 signature"] D --> E["Reject unsigned package"] end installer --> F["Stack buffer overflow and ROP"] style installer fill:transparent,stroke:transparent

The signature check still rejected the package eventually. But by then, the archive extractor had already run on the attacker-controlled archive.

Inside the type-4 archive, each entry contains a filename. The vulnerable extractor used that filename to construct a temporary extraction path, something like:

char temporary_directory[0x98];

strcpy(temporary_directory, archive_controlled_directory);
strcat(temporary_directory, "/tmpXXXXXX");
mkstemp(temporary_directory);

There was no bounds check, stack canary, or anything stopping a simple buffer overflow in the strcpy().

A path beginning under /rw/pckg can overflow this 152-byte stack buffer. Repeated ./ path components made the raw string long enough to reach the saved return address, while still resolving back to an existing attacker controlled location after path normalization.

flowchart TD A["Archive filename"] --> B["Long directory string: /rw/pckg/././././././..."] B --> C["strcpy() into a 152-byte stack buffer"] C --> D["Saved EIP overwritten at offset 156"] D --> E["ROP chain in the privileged installer process"]

The extractor then tried to choose a place for its temporary file. It called statfs() on the attacker-controlled lexical path. statfs() only needs an existing object on a filesystem; it succeeds for a regular file as well as for a directory.

Sol therefore planted a regular file in /rw/pckg whose filename contained the long byte sequence used for the overflow. The archive path used many ./ components: they made the raw path string long enough to overwrite the stack, while path normalization still resolved it to that existing anchor file.

After the unbounded strcpy() had already overwritten the saved return address, the extractor appended /tmpXXXXXX and called mkstemp(). That attempted to create a child below the anchor file, so it failed because the anchor was not a directory. But by that time, the return address has been already overwritten.

Like the dhcp binary, the installer is a fixed-address x86 binary with NX enabled, without canaries. Sol therefore could use a simple ROP chain.

The final chain did four things:

  1. chdir("/rw/pckg/")
  2. chmod(".auto.npk", 0777)
  3. derive execve() from an already resolved libc GOT entry
  4. execve(".auto.npk", ...)

.auto.npk is not another package. It is a small ELF payload written earlier by the WebFig primitive. It created a marker outside WebFig’s permitted write directories, then copied /proc/self/status and the RouterOS version file into a WebFig-readable directory.

/proc/self/status exfiltrated through the exploit confirmed this was running as root inside the .auto.npk process:

Name:	.auto.npk
Umask:	0022
State:	R (running)
Tgid:	259
Ngid:	0
Pid:	259
PPid:	257
TracerPid:	0
Uid:	0	0	0	0
Gid:	0	0	0	0
FDSize:	64
Groups:	
[continued]

This resulted in a pre-auth persistent RCE on a stock RouterOS installation, with one important limitation: the attacker could plant the package immediately, but could not remotely force the required graceful reboot. Making this a delayed RCE.

This chain was completed just two hours after the DHCP RCE was exploited, after I pointed out that the DHCP path did not work on the default configuration. That was around 13 hours after Sol took over.

Patched: plot thickens

I reported this to MikroTik. Their response suggested that they already knew about the WebFig finding, because it had been patched in 7.24.1 by the time I reported it.4

The DHCP buffer overflow was not patched, but they stated that they would patch it in the next release. That release followed a few days later as 7.24.2 and was marked as security-relevant, with MikroTik even sending push notifications encouraging everyone to update.

Important security update
Important security update

Looking at the version diff produced, I however noticed that my bugs were not the only thing they patched. There were also big changes to the SSH subsystem and an IoC check for the user ops.

That made me suspect I was not the first person looking at MikroTik. This was confirmed soon after when CERT Polska released their report and the technical writeup. It covered an SSH authentication-bypass bug, CVE-2026-86060, which had been exploited in the wild5, as well as the WebFig file read/write bug, CVE-2026-67281.6

Sol and I missed the SSH bug, likely because initially Deepseek v4 Flash didn’t include it in the research scope as a by-default exposed service.

In 7.24.2 the DHCP patch looked solid, the NPK installer BOF was still there, but since it was not directly exploitable, that was fine. What was not fine, is that while they patched the original Webfig bug, they introduced a new one.

Webfig again, crypto edition: Session takeover to arbitrary file read/write

MikroTik fixed the original WebFig exploit in 7.24.1. The sequence-wrap crash no longer restarted www, and the JsProxy session constructor now explicitly cleared the previously uninitialized authorization pointer.

They also replaced the old stream-cipher framing with X25519 key exchange and AES-CTR.

At first glance, this looked like a solid fix. However, it introduced a new kind of problem.

The new protocol encrypted each client-to-server frame like this:

[ session ID ][ sequence ][ IV ][ ciphertext ]
      4 B         4 B      16 B    variable

The session ID, sequence number, and AES-CTR IV were all outside the ciphertext. The server still took the supplied session ID, looked it up in a global session tree, and authorized the resulting session.

It never checked that this session belonged to the HTTP connection which delivered the frame.

Conceptually, the new bug looked like this:

session = global_sessions[frame.session_id];

if (session->authenticated &&
    session->authorization->permissions & FILE_PERMISSION)
    handle_file_request(session, frame);

An anonymous attacker could therefore submit a frame using the numeric ID of a different, authenticated WebFig session. The attacker did not need anything sensitive to do that.

They did need a currently active WebFig user with file permissions.

This makes this finding conditional: it was still pre-authentication from the attacker’s perspective, but an idle router with no logged in WebFig user was not exploitable.

AES-CTR is encryption, not authentication

AES-CTR turns a block cipher into a stream cipher:

ciphertext = plaintext XOR keystream

It keeps data secret only while the attacker cannot obtain the keystream. It does not prove who created a message or prevent modification. A secure protocol normally adds a MAC or uses authenticated encryption such as AES-GCM.

RouterOS’s new framing had neither message authentication nor a server-controlled nonce. The client supplied the IV.

That was the cryptographic mistake. But it still did not immediately reveal an AES keystream: the path was encrypted, and the attacker did not know the victim’s key.

The required next piece for that was the multipart upload handler.

It accepted a JsProxy frame with only the 24-byte header:

[ victim session ID ][ fresh sequence ][ arbitrary IV ]

There was no ciphertext. The decryptor accepted this as a zero-length plaintext, which was a valid empty base directory. Then the multipart parser read the filename and file contents from ordinary cleartext HTTP multipart metadata.

With illustrative values, one such request looked like this. The query string decodes to exactly 24 bytes: four bytes of session ID, four bytes of sequence number, and a 16-byte IV.

POST /jsproxy/upload?%12%34%56%78%00%00%40%00%DE%AD%BE%EF%00%11%22%33%44%55%66%77%88%99%AA%BB HTTP/1.1
Host: 192.168.1.1
Content-Type: multipart/form-data; boundary=WEBFIG-random

--WEBFIG-random
Content-Disposition: form-data; name="file"; filename="AAA"
Content-Type: application/octet-stream

known contents
--WEBFIG-random--

We cannot simply use the filename for path traversal, because / and \ were forbidden in it. The path traversal is in the directory selection, which is in the encrypted query string.

But we can create arbitrary files with arbitrary filenames in the current directory.

So an anonymous attacker could create files named A, AA, AAA, and so on in the authenticated victim’s Files store. Why would files like that be interesting? Readers who have exploited a crypto keystream oracle before may already know.

flowchart TD A["Anonymous attacker"] --> B["Selects an active, authenticated WebFig session ID"] B --> C["Sends an empty encrypted path with an attacker-chosen IV"] C --> D["Multipart parser accepts cleartext filename and body"] D --> E["Creates predictable files under the victim's authorization"]

The session IDs were monotonically allocated. Sol created an anonymous session, then scanned a small range of older IDs. A 200 response to the zero-length multipart request identified a live authenticated session ID.

Turning uploads into a keystream oracle

Once the attacker could create known files, the remaining AES-CTR problem became an equality oracle.

For a fixed IV, Sol uploaded files with predictable names and contents. It then sent forged encrypted download requests under the victim session ID. For each unknown keystream byte, it tried all 256 ciphertext byte values and observed which one caused the server to select the known file.

That recovered one AES-CTR keystream byte at a time:

flowchart TD A["Known target filename: AAAAAA..."] --> B["Try candidate ciphertext byte values 00 through ff at a given offset"] B --> C["Server response identifies the value that decrypts to the expected path"]

This did not break AES or recover the victim’s AES key. It recovered the keystream for one attacker-selected IV, which is sufficient because CTR encryption is XOR:

chosen-path ciphertext = desired pathname XOR recovered keystream

With this, Sol could recover arbitrarily long part of the key stream.7 That was enough to encrypt an arbitrary PUT destination path and write there under the victim’s authorization.

In particular, it could write below ../pckg/ again.

That allowed Sol to plant packages and exploit the installer again.

Back to the installer

With the NPK installer bug still present in 7.24.2, this was easy to exploit.

The resulting chain had two practical conditions:

  • NEW: an authenticated WebFig session with file permissions had to be alive
  • the attacker had to wait for a normal graceful reboot or shutdown

See the PoC video here:

Patched again

MikroTik finally fixed both halves of this chain in RouterOS 7.24.4.

On the WebFig side, the important change was adding authentication to the encrypted JsProxy frames.

The new frame format is:

[session ID][sequence][IV][HMAC-SHA256][AES-CTR ciphertext]

www now derives separate encryption and signing keys from the X25519 session exchange, then verifies a 32-byte HMAC over the session ID, sequence number, and decrypted plaintext before processing the request. A valid tag from an anonymous session cannot be transplanted to an authenticated session.

The file-upload handler also now rejects the authenticated empty-path request that formed the keystream oracle in the previous exploit.

The session lookup gained an additional source-address check too: a session is now associated with the client IP address that created it. That helps, although the HMAC is the meaningful security boundary: two users behind the same NAT can still share an address.

They also changed installer. It now constructs the path with dynamic string operations.

Observations & Takeaways

GPT 5.6 Sol is a beast

The way it can chain things, reason about primitives and use them, is truly amazing. But we should already know that.

DeepSeek would need a better harness to shine

It certainly is not a bad model. For finding vulnerabilities it’s pretty good. But not so much for actually using them, chaining them and evaluating them - which is often much more useful.

Weaker models need a harness, stronger models need a manager

Smart, large models (like Sol) are able to work independently and autonomously. And are able to do the things the harnesses are usually used for: setting up a feedback loop, verify the findings, document things, subagent coordination, you name it.

But they still need to know the goal and what you want from them.

Specifying that is sometimes surprisingly hard. Following the garbage-in, garbage-out principle, bad output is often the result of a bad prompt or environment.

Did the model not audit the whole codebase, but just a single file? Did the model not consider this threat model or that vulnerability class? Did you tell it do so?

Did it cheat? Did it delete an important file? Did you tell it NOT to do so?

But even with the best prompt, the model will sometimes behave stupidly and make mistakes.

Write code that is broken, find (or even verify) a vulnerability that is no-issue, delete the whole working directory, or the whole system.

That’s why for best results it needs a manager, or a partner, who will set the direction, make sure it follows the direction, resolve problems that it by itself cannot solve, ask curious questions, and so on.

That is where I see the role of humans in security research in the near future.8

There are many more bugs out there

I’m certain there are many, many more bugs like this out there, even worse, more impactful.

From HEIF heist, to what’s happening inside Linux, Android, Chromium, or Firefox in the past few months. Or bug bounties!

There is still plenty more to be found and fixed.

Ideally, by AIs run by ethical researchers and not bad actors. It was, in my opinion, never more necessary and a better time to secure software and hardware than today.

We will see how it will look like in 10 years, but the transition period from human to superhuman cybersecurity has still only begun recently.

No need to wait for Mythos, Daybreak Red, or whatever, you can do this now

The headlines sometimes give the impression that it is necessary to have access to the restricted models, or spend thousands of dollars, to find vulnerabilities now. While both certainly help, they are not necessary at all.

Open-weight models do not have strong guardrails and are widely available at low cost.9 Getting Daybreak Blue access to OpenAI models is also not hard for most people.10

Overall, I think it is a good thing. Defenders just need to know about this too and use those models faster and at greater scale than bad actors. This will ultimately raise the security of software and hardware for everyone – or so I hope.

The more things we patch now, the less there will be for Skynet to find :)

You still need to understand a bit of what you (or more specifically the models) are doing to be effective in AI security research. Otherwise, you might end up hunting just ghosts, overwhelming triagers or the maintainers with BS findings, or even some worse things.

But the PoC||GTFO principle still applies here. Are you a developer who let a model hunt vulnerabilities on an open source app, it produced an RCE exploit which you are not entirely sure how works, but you are still able to verify that the exploit is not “cheating”? You should report it. Or even better, let the AI explain the exploit so you understand it and become a better developer in the process.

LLMs took the fun out of CTFs and brought it to bug bounty & 0day hunting

This is a quite from my friend @nightshiba.

And I find it true.

As someone who has been doing research similar to the one described in this post for a while now, I can say that it is quite fun. In a different way that CTFs were (solving a hard challenge after grinding for hours by hand was still more satisfiable), but definitely more fun (to me) than bug bounties or blind security research was before AI.

The things that I didn’t like doing can now be automated (blind guessing, deobfuscation, monkey-testing API endpoints, …) and what remains are the interesting bits I like doing (strategizing, making and discussing hypothesis, finding holes in threat models, chaining things, …).

You are also in control of where this border is (although laziness is a problem for sure). And you are free to combine the power of you with the power of AI as you like to get the result you want.

Update everything always as fast as possible

Even when the release is not marked as security relevant.

Timeline

  • 8 Aug 2026: I set the original /goal in Claude Code and started DeepSeek v4 Flash 0731 on RouterOS 7.23.3.
  • 8–10 Aug 2026: DeepSeek unpacked and compared the NPKs, mapped the exposed attack surface (missed SSH), set up the x86 QEMU VM, and investigated.
  • 10 Aug 2026: A DeepSeek Flash subagent spent about one hour on WebFig and found the JsProxy file-write surface and session-selection issue. It did not complete a credential-free exploit chain.
  • 11 Aug 2026, 09:30 UTC: I asked GPT-5.6 Sol in Codex to continue Deepseek’s work and adopt its original goal.
  • 11 Aug 2026, 21:00 UTC: Sol confirmed the first complete RCE: the DHCP relay stack overflow. It required an enabled and reachable DHCP relay, so it was not a fresh-install-default path.
  • 11 Aug 2026, 21:03 UTC: I pointed out the DHCP relay limitation and asked Sol to continue looking for a default-facing pre-authentication chain.
  • 11 Aug 2026, 21:50–23:00 UTC: Sol returned to DeepSeek’s WebFig lead, made the file-write primitive credential-free, found the NPK installer stack overflow, and built the complete WebFig -> installer RCE chain.
  • 11 Aug 2026, 23:00 UTC: Sol has a successful exploit for Webfig: package plant followed by a graceful shutdown resulted in root execution.
  • 12–17 Aug 2026: I used DeepSeek Flash, Sol and my knowledge to improve the verifier, document the findings, test exploitation assumptions, and investigate immediate-execution variants. Also explored and disproved few other potential findings.
  • 14 Aug 2026: RouterOS 7.24 is released and changes the Webfig authentication implementation, likely patching the Webfig bug.
  • 17 Aug 2026: I sent an email with the report to MikroTik’s security contact, security@mikrotik.com.11
  • 27 Aug 2026: MikroTik replies that the Webfig bug has been already fixed and that they are gonna fix the DHCP finding soon.
  • 2 Sep 2026: Someone exploits the whole internet of MikroTiks with the SSH bug.
  • 3 Sep 2026: RouterOS 7.24.2 is released and patches the DHCP relay bug, but not the NPK installer bug. It is marked as security relevant.
  • 4 Sep 2026: I asked Sol to verify the fixes and search RouterOS 7.24.2 for a bypass or a new chain.
  • 4 Sep 2026: Sol found the new AES-CTR WebFig bug and chained it with the still vulnerable installer.
  • 5 Sep 2026: I sent a report for the new RCE chain.
  • 5 Sep 2026: Cert.pl releases their blogpost.
  • 7 Sep 2026: MikroTik acknowledges the new chain and says it will be fixed in the next release.
  • 15 Sep 2026: RouterOS 7.24.3 was released and patches both Webfig and installer
  • 25 Sep 2026: This blog post is released.

  1. This means fewer incentives for external researchers to examine (or spend tokens on) their products. ↩︎

  2. It used objdump and Ghidra, writing simple Java wrappers around the decompiler. ↩︎

  3. This is common in corporate environments. ↩︎

  4. That release was not marked as security-relevant. ↩︎

  5. This explained the ops user indicator of compromise noted in the version diff. ↩︎

  6. It seems CERT Polska had only made the file-read portion of this bug work. ↩︎

  7. To give you an illustration, to recover 248 bytes it took about 30000 HTTP requests. ↩︎

  8. That partner could itself be an AI, but that merely shifts the management problem and abstraction one level up. ↩︎

  9. I especially recommend looking into GLM-5.3, DeepSeek v4.1 Flash, or Kimi K3. ↩︎

  10. I used its access to GPT-5.6 Sol for this post. ↩︎

  11. The security contact page is https://mikrotik.com/supportsec. ↩︎


Published on
pwn 0day writeup

Latest Posts