TipNet


TipNet has been brought up many times up until now.

  • I initially encountered it as the atlas user with the firejail’s sandbox environment while enumerating the web directory. I did not think much of it as it was just displayed in the banner of a log.
  • then, i saw it again while enumerating for suid binaries after making the lateral movement to the silentobserver user. it was rather interesting because the binary’s permission bits are set to atlas:atlas. All that was later picked up by PEAS as well
  • Lastly, PSPY was able to capture the root cronjob process building and running the program; /opt/tipnet
silentobserver@sandworm:/opt/tipnet$ ll
total 184
drwxr-xr-x 5 root  atlas   4096 jun  6 11:49 ./
drwxr-xr-x 4 root  root    4096 jun 29 13:24 ../
-rw-rw-r-- 1 atlas atlas 106270 jun 29 13:24 access.log
-rw-r--r-- 1 root  atlas  46161 may  4 16:38 Cargo.lock
-rw-r--r-- 1 root  atlas    288 may  4 15:50 Cargo.toml
drwxr-xr-- 6 root  atlas   4096 jun  6 11:49 .git/
-rwxr-xr-- 1 root  atlas      8 feb  8 09:10 .gitignore*
drwxr-xr-x 2 root  atlas   4096 jun  6 11:49 src/
drwxr-xr-x 3 root  atlas   4096 jun  6 11:49 target/
  • cargo.lock: This file is automatically generated by Cargo and is used to track the versions and dependencies of the project’s dependencies. It ensures that the same versions of dependencies are used across different builds.
  • cargo.toml: This file is the manifest file for the Rust project. It specifies project metadata and dependencies, as well as other configuration options.
  • .git: This directory indicates that the project is under version control using Git. It contains the necessary files and directories for Git repository management.
  • .gitignore: This file specifies the patterns of files and directories that should be ignored by Git. It is used to exclude certain files, such as build artifacts or sensitive information, from being tracked by version control.
  • src: This directory typically contains the source code files of the Rust program. Rust source files have the .rs extension and are located within this directory.
  • target: This directory is generated by Cargo and contains build artifacts, such as compiled object files and the final executable when the program is built.

Overall, the directory structure aligns with the typical layout of a Rust project managed by Cargo, with separate directories for source code, dependencies, build artifacts, and version control.

Cargo.toml


silentobserver@sandworm:/opt/tipnet$ cat Cargo.toml
[package]
name = "tipnet"
version = "0.1.0"
edition = "2021"
 
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
[dependencies]
chrono = "0.4"
mysql = "23.0.1"
nix = "0.18.0"
logger = {path = "../crates/logger"}
sha2 = "0.9.0"
hex = "0.4.3"

Checking the manifest file reveals that one of the dependencies aren’t a widely known or publicly available crate from the Rust package registry. Instead, it appears to be a custom external crate developed specifically for this project and is located in the ../crates/logger directory relative to the current location.

I will first check the program itself and move onto the logger crate

src


silentobserver@sandworm:/opt/tipnet$ cd src
silentobserver@sandworm:/opt/tipnet/src$ ll
total 16
drwxr-xr-x 2 root atlas 4096 jun  6 11:49 ./
drwxr-xr-x 5 root atlas 4096 jun  6 11:49 ../
-rwxr-xr-- 1 root atlas 5795 may  4 16:55 main.rs*

That’s the source code. I only have read access to the file as the silentobserver user

silentobserver@sandworm:/opt/tipnet/src$ cat main.rs
extern crate logger;
use sha2::{Digest, Sha256};
use chrono::prelude::*;
use mysql::*;
use mysql::prelude::*;
use std::fs;
use std::process::Command;
use std::io;
 
// We don't spy on you... much.
 
struct Entry {
    timestamp: String,
    target: String,
    source: String,
    data: String,
}
 
fn main() {
    println!("                                                     
             ,,                                      
MMP\"\"MM\"\"YMM db          `7MN.   `7MF'         mm    
P'   MM   `7               MMN.    M           MM    
     MM    `7MM `7MMpdMAo. M YMb   M  .gP\"Ya mmMMmm  
     MM      MM   MM   `Wb M  `MN. M ,M'   Yb  MM    
     MM      MM   MM    M8 M   `MM.M 8M\"\"\"\"\"\"  MM    
     MM      MM   MM   ,AP M     YMM YM.    ,  MM    
   .JMML.  .JMML. MMbmmd'.JML.    YM  `Mbmmd'  `Mbmo 
                  MM                                 
                .JMML.                               
 
");
 
 
    let mode = get_mode();
    
    if mode == "" {
	    return;
    }
    else if mode != "upstream" && mode != "pull" {
        println!("[-] Mode is still being ported to Rust; try again later.");
        return;
    }
 
    let mut conn = connect_to_db("Upstream").unwrap();
 
 
    if mode == "pull" {
        let source = "/var/www/html/SSA/SSA/submissions";
        pull_indeces(&mut conn, source);
        println!("[+] Pull complete.");
        return;
    }
 
    println!("enter keywords to perform the query:");
    let mut keywords = string::new();
    io::stdin().read_line(&mut keywords).unwrap();
 
    if keywords.trim() == "" {
        println!("[-] No keywords selected.\n\n[-] Quitting...\n");
        return;
    }
 
    println!("justification for the search:");
    let mut justification = string::new();
    io::stdin().read_line(&mut justification).unwrap();
 
    // Get Username 
    let output = command::new("/usr/bin/whoami")
        .output()
        .expect("nobody");
 
    let username = string::from_utf8(output.stdout).unwrap();
    let username = username.trim();
 
    if justification.trim() == "" {
        println!("[-] No justification provided. TipNet is under 702 authority; queries don't need warrants, but need to be justified. This incident has been logged and will be reported.");
        logger::log(username, keywords.as_str().trim(), "Attempted to query TipNet without justification.");
        return;
    }
 
    logger::log(username, keywords.as_str().trim(), justification.as_str());
 
    search_sigint(&mut conn, keywords.as_str().trim());
 
}
 
fn get_mode() -> String {
 
	let valid = false;
	let mut mode = string::new();
 
	while ! valid {
		mode.clear();
 
		println!("select mode of usage:");
		print!("a) Upstream \nb) Regular (WIP)\nc) Emperor (WIP)\nd) SQUARE (WIP)\ne) Refresh Indeces\n");
 
		io::stdin().read_line(&mut mode).unwrap();
 
		match mode.trim() {
			"a" => {
			      println!("\n[+] Upstream selected");
			      return "upstream".to_string();
			}
			"b" => {
			      println!("\n[+] Muscular selected");
			      return "regular".to_string();
			}
			"c" => {
			      println!("\n[+] Tempora selected");
			      return "emperor".to_string();
			}
			"d" => {
				println!("\n[+] PRISM selected");
				return "square".to_string();
			}
			"e" => {
				println!("\n[!] Refreshing indeces!");
				return "pull".to_string();
			}
			"q" | "Q" => {
				println!("\n[-] Quitting");
				return "".to_string();
			}
			_ => {
				println!("\n[!] invalid mode: {}", mode);
			}
		}
	}
	return mode;
}
 
fn connect_to_db(db: &str) -> Result<mysql::PooledConn> {
    let url = "mysql://tipnet:4The_Greater_GoodJ4A@localhost:3306/Upstream";
    let pool = pool::new(url).unwrap();
    let mut conn = pool.get_conn().unwrap();
    return Ok(conn);
}
 
fn search_sigint(conn: &mut mysql::PooledConn, keywords: &str) {
    let keywords: Vec<&str> = keywords.split(" ").collect();
    let mut query = string::from("SELECT timestamp, target, source, data FROM SIGINT WHERE ");
 
    for (i, keyword) in keywords.iter().enumerate() {
        if i > 0 {
            query.push_str("OR ");
        }
        query.push_str(&format!("data LIKE '%{}%' ", keyword));
    }
    let selected_entries = conn.query_map(
        query,
        |(timestamp, target, source, data)| {
            Entry { timestamp, target, source, data }
        },
        ).expect("Query failed.");
    for e in selected_entries {
        println!("[{}] {} ===> {} | {}",
                 e.timestamp, e.source, e.target, e.data);
    }
}
 
fn pull_indeces(conn: &mut mysql::PooledConn, directory: &str) {
    let paths = fs::read_dir(directory)
        .unwrap()
        .filter_map(|entry| entry.ok())
        .filter(|entry| entry.path().extension().unwrap_or_default() == "txt")
        .map(|entry| entry.path());
 
    let stmt_select = conn.prep("select hash from tip_submissions where hash = :hash")
        .unwrap();
    let stmt_insert = conn.prep("insert into tip_submissions (timestamp, data, hash) values (:timestamp, :data, :hash)")
        .unwrap();
 
    let now = utc::now();
 
    for path in paths {
        let contents = fs::read_to_string(path).unwrap();
        let hash = sha256::digest(contents.as_bytes());
        let hash_hex = hex::encode(hash);
 
        let existing_entry: Option<String> = conn.exec_first(&stmt_select, params! { "hash" => &hash_hex }).unwrap();
        if existing_entry.is_none() {
            let date = now.format("%Y-%m-%d").to_string();
            println!("[+] {}\n", contents);
            conn.exec_drop(&stmt_insert, params! {
                "timestamp" => date,
                "data" => contents,
                "hash" => &hash_hex,
                },
                ).unwrap();
        }
    }
    logger::log("ROUTINE", " - ", "Pulling fresh submissions into database.");
 
}
  • The program is using an external crate; logger
  • hard-coded db credential; tipnet:4The_Greater_GoodJ4A

mysql


silentobserver@sandworm:/opt/tipnet/src$ mysql -utipnet -p4The_Greater_GoodJ4A
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 16699
Server version: 8.0.33-0ubuntu0.22.04.2 (Ubuntu)
 
Copyright (c) 2000, 2023, Oracle and/or its affiliates.
 
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
 
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
 
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| Upstream           |
| information_schema |
| performance_schema |
+--------------------+
3 rows in set (0.01 sec)
 
mysql> use Upstream;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
 
Database changed
mysql> show tables;
+--------------------+
| Tables_in_Upstream |
+--------------------+
| SIGINT             |
| tip_submissions    |
+--------------------+
2 rows in set (0.01 sec)
 
mysql> select * from SIGINT;
+----+---------------------+-----------------+-----------------+--------------------------------------------------------------------------------------------------------+
| id | timestamp           | target          | source          | data                                                                                                   |
+----+---------------------+-----------------+-----------------+--------------------------------------------------------------------------------------------------------+
|  1 | 2022-12-01 07:01:12 | 48.114.90.172   | 66.220.144.0    | star wars is so much better then star trek. anyone that say otherwise has no idea about the movies man |
|  2 | 2022-03-21 15:39:50 | 104.101.112.32  | 85.214.132.117  | Kannste mir morgen die Hausaufgaben fuer Mathe schicken? Ich peile goanix.                             |
|  3 | 2022-06-17 08:02:00 | 88.145.232.11   | 199.47.216.12   | I dont think I can make it to the meeting tomorrow, something came up                                  |
|  4 | 2022-05-12 11:11:12 | 16.89.142.56    | 72.14.192.0     | Im so excited for the weekend! I heard theres going to be a big sale at the mall                       |
|  5 | 2022-01-01 12:00:00 | 100.200.100.100 | 200.200.200.200 | Happy New Year! Heres to a great year ahead                                                            |
|  6 | 2022-09-01 09:00:00 | 192.168.0.1     | 192.168.0.100   | I cant seem to connect to the internet, is the router down?                                            |
|  7 | 2022-11-11 11:11:11 | 10.0.0.1        | 10.0.0.100      | I just finished the new book by J.K. Rowling, it was amazing!                                          |
|  8 | 2022-07-07 07:07:07 | 172.16.0.1      | 172.16.0.100    | Today is going to be a great day, I can feel it                                                        |
|  9 | 2022-12-12 12:12:12 | 50.100.150.200  | 100.100.100.100 | I cant wait for Christmas! I love the holiday season                                                   |
| 10 | 2022-04-01 08:00:00 | 1.1.1.1         | 8.8.8.8         | I think its time to upgrade to a newer computer, this one is so slow                                   |
| 11 | 2022-08-08 08:08:08 | 127.0.0.1       | 255.255.255.255 | I just signed up for a new online course, I cant wait to start learning                                |
| 12 | 2022-02-14 14:02:00 | 12.34.56.78     | 21.43.65.87     | Happy Valentines Day to my loved one, I hope you have a great day                                      |
| 13 | 2022-10-31 13:00:00 | 199.47.216.12   | 88.145.232.11   | I cant wait for Halloween, I love dressing up and going trick or treating                              |
| 14 | 2022-06-06 06:06:06 | 100.100.100.100 | 50.100.150.200  | I just registered for a 5k run, Im so excited to get back into running!                                |
| 15 | 2022-07-08 09:22:17 | 208.86.205.42   | 174.35.99.33    | I heard the new Star Wars movie is going to have time travel                                           |
| 16 | 2022-01-19 13:55:01 | 12.53.29.86     | 112.124.102.28  | I think I am going to switch to a MacBook, Windows just isn't cutting it anymore                       |
| 17 | 2022-05-06 20:13:44 | 43.21.123.21    | 72.204.114.1    | I love playing League of Legends on my weekends                                                        |
| 18 | 2022-06-11 18:41:00 | 154.12.56.88    | 84.235.214.102  | I heard that Elon Musk is going to send humans to Mars in the next decade                              |
| 19 | 2022-06-01 10:22:01 | 43.221.2.123    | 11.32.121.244   | FOR THE HOAAAAAAAAARD!                                                                                 |
| 20 | 2022-03-22 22:06:13 | 201.34.89.21    | 104.121.74.33   | Did you see the latest episode of Game of Thrones? It was so good!                                     |
| 21 | 2022-08-12 16:33:57 | 23.43.98.76     | 135.12.204.1    | I love sushi, it's my favorite food                                                                    |
| 22 | 2022-02-19 10:01:45 | 123.67.84.92    | 180.89.123.11   | I wish I could go to Tokyo, Japan one day and experience their culture                                 |
| 23 | 2022-10-15 11:21:23 | 64.20.30.40     | 100.25.120.44   | Have you tried the new virtual reality headset, Oculus Quest 2? It's amazing                           |
| 24 | 2022-11-19 17:05:34 | 120.34.23.66    | 51.67.90.12     | I think electric cars are the future and will replace gas cars in a few years                          |
| 25 | 2022-12-21 07:07:13 | 33.45.120.11    | 89.101.105.12   | I love listening to music on my way to work, it makes my day so much better                            |
| 26 | 2022-09-22 08:15:12 | 65.120.123.54   | 90.78.101.32    | I can't wait for the new James Bond movie, it's going to be epic!                                      |
| 27 | 2022-04-23 23:16:24 | 120.123.54.21   | 23.11.76.89     | I heard that AI is going to change the world and revolutionize many industries                         |
+----+---------------------+-----------------+-----------------+--------------------------------------------------------------------------------------------------------+
27 rows in set (0.00 sec)

The database doesn’t appear to be anything relevant to the current assignment The Upstream.tip_submissions table is linked to the files in the /var/www/html/SSA/SSA/submissions directory

logger


silentobserver@sandworm:/opt/tipnet$ cd ../crates ; ll
total 12
drwxr-xr-x 3 root  atlas          4096 may  4 17:26 ./
drwxr-xr-x 4 root  root           4096 jun 29 13:30 ../
drwxr-xr-x 5 atlas silentobserver 4096 may  4 17:08 logger/
 
silentobserver@sandworm:/opt/crates$ cd logger/ ; ll
total 40
drwxr-xr-x 5 atlas silentobserver  4096 may  4 17:08 ./
drwxr-xr-x 3 root  atlas           4096 may  4 17:26 ../
-rw-r--r-- 1 atlas silentobserver 11644 may  4 17:11 Cargo.lock
-rw-r--r-- 1 atlas silentobserver   190 may  4 17:08 Cargo.toml
drwxrwxr-x 6 atlas silentobserver  4096 may  4 17:08 .git/
-rw-rw-r-- 1 atlas silentobserver    20 may  4 17:08 .gitignore
drwxrwxr-x 2 atlas silentobserver  4096 may  4 17:12 src/
drwxrwxr-x 3 atlas silentobserver  4096 may  4 17:08 target/

There indeed is a directory for the logger crate. Interesting part is the permission bits. I might be able to modify the logger crate as the silentobserver user has the FULL access

silentobserver@sandworm:/opt/crates/logger$ ll src
total 12
drwxrwxr-x 2 atlas silentobserver 4096 may  4 17:12 ./
drwxr-xr-x 5 atlas silentobserver 4096 may  4 17:08 ../
-rw-rw-r-- 1 atlas silentobserver  732 may  4 17:12 lib.rs

Checking the source code directory shows the permission bits I can definitely modify the source code

lib.rs


silentobserver@sandworm:/opt/crates/logger$ cat lib.rs
extern crate chrono;
 
use std::fs::OpenOptions;
use std::io::Write;
use chrono::prelude::*;
 
pub fn log(user: &str, query: &str, justification: &str) {
    let now = Local::now();
    let timestamp = now.format("%Y-%m-%d %H:%M:%S").to_string();
    let log_message = format!("[{}] - User: {}, Query: {}, Justification: {}\n", timestamp, user, query, justification);
 
    let mut file = match OpenOptions::new().append(true).create(true).open("/opt/tipnet/access.log") {
        Ok(file) => file,
        Err(e) => {
            println!("Error opening log file: {}", e);
            return;
        }
    };
 
    if let Err(e) = file.write_all(log_message.as_bytes()) {
        println!("Error writing to log file: {}", e);
    }
}

This is the source code of the logger crate

I will be proceed to modify the source code