Common Lisp (well, mostly - heavily reliant on cURL) solution for a cybersec automation game. URL Redacted on request of the company.
Find a file
2024-05-18 11:58:13 +02:00
cl-secninja.lisp Redacting 2024.05.18 2024-05-18 11:58:13 +02:00
README.org Redacting 2024.05.18 2024-05-18 11:58:13 +02:00

Readme

Intro

This is a coding task from [redacted], which I found rather fun to tinker with.

This is an org-mode file. The code blocks are tangled using org-babel into cl-secninja.lisp.

Dependencies:

  • curl
  • sbcl with quicklisp

Usage:

  1. Load in SBCL with sbcl --load cl-secninja.lisp.
  2. Run (four)
  3. Copy the base64 encrypted message into a file called email.
  4. Run (base64).
  5. Done.

Code:

Quicklisp:

(ql:quickload '(:cl-ppcre
				:cl-base64
				:ironclad
				:jsown))

Defpackage and in-package

(defpackage #:cl-secninja
  (:use #:common-lisp #:cl #:uiop #:cl-ppcre  #:ironclad #:cl-base64))

(in-package #:cl-secninja)

URL var

The trailing slash is important for consolidating the URL later.

(defvar *url* "redacted")

Run cURL

(defun run-curl (command)
  (let* ((curl-command (concatenate 'string "curl -i " command))
    (result (uiop:run-program curl-command
                              :output :string
                              :error-output T)))
         result))

First page

Grab first page, print out the command and result, and return the parameter string.

(defun one ()
  (let* ((command *url*)
		 (result (run-curl *url*)))
	(format t "~%COMMAND: curl ~a" command)
	(format t "~%RESULT: curl ~a" result)
	(car (ppcre:all-matches-as-strings "\\?.*" result))))

Second page

This function calls (one), so calling it directly for seeing the second page works.

Concatenate the URL with the parameter string from the first function, and grab and return the second page.

(defun two ()
  (let* ((command (concatenate 'string
							   "\"" *url* (one) "\""))
		 (result (run-curl command)))
	result))

Third

This is where we get interesting. This function calls (two), which returns the second page for processing, and then calls (run-curl) on the command it constructs, and returns the output.

I wanted to see what the values of the variables are while testing, thus the print statements and triple let. I know I could consolidate that, but I feel like this way is more transparent than having a (let* (...)) that does everything in the background.

The thing that was most tricky here was getting the double quotes to work properly between this function and (run-curl), because the command string is being passed around, and it itself includes quotes.

(defun three ()
  (let ((headers (ppcre:all-matches-as-strings "X-.*" (two))))
	(print headers)
	(print (car headers))
	(print (cadr headers))
	(let ((command (concatenate 'string
								"\"" *url* "\?step=2" "\""
								" -H \"" (car headers) "\""
								" -H \"" (cadr headers) "\"")))
	  (let ((result (run-curl command)))
		(format t "~%RESULT: curl ~a" result)
		result))))

Four

This was the most fun one, given all the processing. I can't say that the loop worked the first time around.

After running (four), your terminal will print out the base64 encoded string. I can't say I want to mess with extracting the base64 string with another regexp, so I just manually copied the string into a file called email.

Then…

(defun four ()
  (let*
	  ((data (three))
	   (input
		 (car (ppcre:all-matches-as-strings "(?sm)\{.*}$" data)))
	   (challenge
		 (string-left-trim "challenge: " (car (ppcre:all-matches-as-strings "challenge:(.*)" data))))
	   (timestamp
		 (string-left-trim "timestamp: " (car (ppcre:all-matches-as-strings "timestamp:(.*)" data))))
	   (sorted (sort (cdr (jsown:parse input)) #'string<= :key #'first))
	   (connected
		 (string-right-trim "\&"
							(format nil "~{~A~}"
									(loop
									  for item in sorted
									  collect
									  (concatenate 'string  (car item) "\=" (cdr item) "\&")))))
	   (hash (sha-256 connected))
	   (command
		 (string-right-trim "\-"
							(concatenate 'string
										 "-X POST \"" *url* "\?step=3" "\""
										 " -H \"Content-Type: application/x-www-form-urlencoded\" "
										 " -d \"challenge=" challenge "\""
										 " -d \"timestamp=" timestamp "\""
										 " -d \"hash=" hash "\""
										 ))))
	(terpri)
	(print challenge)
	(print timestamp)
	(print hash)
	(terpri)
	(format t "~%COMMAND: curl ~a" command)
	(terpri)
	(let ((result (run-curl command)))
	  (format t "~%RESULT: curl ~a" result))
	(terpri)))

Sha256

Util function for the above code.

(defun sha-256 (str)
  (ironclad:byte-array-to-hex-string
   (ironclad:digest-sequence :sha256
							 (ironclad:ascii-string-to-byte-array str))))

Base64

Put the contents of the file into a variable.

(defvar *email* (alexandria:read-file-into-string "email"))

Then, run the base64 decoder over it until the string can't be decoded any more.

(defun base64 ()
  (let ((email *email*))
	(loop repeat 100 do
	  (if (ppcre:scan "@" email)
		  (print email)
		  (setf email (cl-base64:base64-string-to-string email))))))