About

Hello, I'm pwnwriter, and this is a frontend for my compilation of random notes gathered throughout the year. Here, you'll discover a diverse array of notes spanning topics such as Cybersecurity, shell scripting, rust, and much more, all available under the MIT license. These notes are straightforward, nothing but raw insights into various subjects.

Website : pwnwriter.xyz

Github : @pwnwriter

Twitter : @pwnwriter

Ko-fi : @pwnwriter

cat

Allow rust-analyzer to run on single file for completion

Create a rust-project.json in your directory

{
    "sysroot_src": "path/to/the/library",
    "crates": [
        {
            "root_module": "main.rs",
            "edition": "2021",
            "deps": []
        }
    ]
}

Here, i manually defined rustup home. By default it's ~/.rustup

export RUSTUP_HOME="${XDG_DATA_HOME:-$HOME/.local/share}/rustup"

Define crates deps inside a single file.

//! ```cargo
//! [dependencies]
//! clap = { version = "4.2", features = ["derive"] }
//! ```

extern crate clap;

use clap::Parser;

#[derive(Parser, Debug)]
#[clap(version)]
struct Args {
    #[arg(short, long, default_value = "PwnWriter")]
    pub name: String,
}

fn main() {
    let args = Args::parse();
    println!("{}", args.name);
}

Iterators

extern crate anyhow;

use anyhow::Result;

fn main() -> Result<()> {
    let ports = vec![1, 3, 4];

    let urls = vec![
        "https://github.com",
        "https://metislinux.org",
        "https://kisslinux.org",
    ];

    let urls_with_ports: Vec<String> = urls
        .iter()
        .zip(&ports)
        .map(|(url, &port)| format!("{}:{}", url, port))
        .collect();

    for url in urls_with_ports {
        println!("{}", url);
    }

    Ok(())
}

Standard Input Using Cursor

Sometimes, you may not have access to concatenate a file and pipe it into your code for testing. In such situations, you can utilize the Cursor module.

use std::io::{Cursor, BufRead, BufReader};

fn main() {
    let cursor = Cursor::new("Hey\npwned\n".to_string());
    let buffered_reader = BufReader::new(cursor);
    for line in buffered_reader.lines() {
        println!("{}", line.unwrap());
    }
}

How to Install Nix on any Linux VPS

Nix is a versatile package manager, programming language, and even a complete Linux distribution. It stands out with its focus on reproducibility and declarative configuration.

Download and Run the Installer

We'll be using determinate system's nix installer

curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- --install
Screenshot 2024-06-26 at 12 53 49

You should have nix installed by now.

nix --version

Screenshot 2024-06-26 at 12 57 01

Let's install a pkg tmux

nix profile install nixpkgs#tmux

Screenshot 2024-06-26 at 12 58 46

we can even install package for temp shell

nix-shell -p git

Screenshot 2024-06-26 at 13 08 29 Screenshot 2024-06-26 at 13 08 37

Installing Home Manager

Finally, we'll use Home Manager, a tool designed to manage the user environment (e.g., dotfiles) in a declarative way using Nix. We'll start by adding the minimal configuration needed to get Home Manager up and running.

First, create a dotfiles directory if you don't have one already (the name of the directory does not matter). Use git init (or your favorite GUI) to initialize a Git repository in the folder. Add the following two files:

mkdir dotfiles; cd dotfiles; git init

Screenshot 2024-06-26 at 13 13 40
{
  description = "Your description here";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    home-manager = {
      url = "github:nix-community/home-manager";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

  outputs = { nixpkgs, home-manager, ... }: {
    homeConfigurations = {
      <Yourname> = home-manager.lib.homeManagerConfiguration {
        pkgs = nixpkgs.legacyPackages.x86_64-linux;
        modules = [
          ./modules
          {
            home.username = "username";
            home.stateVersion = "23.11";
            home.homeDirectory = "/home/username";
          }
        ];
      };

    };
  };
}

Place the above file as flake.nix.

Now the directory tree should look like this

Screenshot 2024-06-26 at 13 18 31

This flake initiales all the modules from modules directory.

For example to configure git, it goes something like this

{ pkgs, ... }:
let
  name = "username";
  email = "main";
in
{
  programs.git = {
    enable = true;
    userName = name;
    userEmail = email;
  };
}

Now we'll need to source it inside modules,

Like lua's init.lua' we have default.nix in nix.

We'll now import out git.nix using modules/default.nix file and it goes something like this.

{ config, ... }:

let
  modules = [
    ./git.nix
  ];
in
{
  imports = modules;

  xdg.dataHome = "${config.home.homeDirectory}/.local/share";
  programs.home-manager.enable = true;
}

We can now build our config using following.

nix run github:nix-community/home-manager -- switch --flake .#your_name
Screenshot 2024-06-26 at 13 24 51

It should generate your config like below.

Screenshot 2024-06-26 at 13 26 58

Installing NixOS on a Hetzner VPS

In this guide, I will explain how to install NixOS on a Hetzner VPS from a local machine using flakes and Nix itself.

Requirements

  • A linux os with the latest systemd, I'm using ubuntu24.04
  • A machine with Nix already installed (I have written a blog post on this topic).
  • Root privileges to run commands.

That's it!

We will use nixos-anywhere and disko to install Nix and create declarable partitions. First, we'll import them into our flake. The directory structure should look like this:

Screenshot 2024-07-02 at 12 38 41

You'll want to change wood to something else, i'm just imagining the host name for myvps as wolf.

  • flake.nix
  {
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
    disko.url = "github:nix-community/disko";
    disko.inputs.nixpkgs.follows = "nixpkgs";
  };

  outputs = { nixpkgs, disko, ... }: {
    nixosConfigurations.wolf = nixpkgs.lib.nixosSystem {
        system = "x86_64-linux";
        modules = [
          disko.nixosModules.disko
          ./configuration.nix
          ./disk-config.nix
        ];
      };
  };
}

We'll import our configuration.nix and disk-config.nix inside our flake. Add your public ssh keys in authorized_keys for accessing the server after installation.

  • configuration.nix
{ modulesPath, config, lib, pkgs, ... }: {
  imports = [
    (modulesPath + "/installer/scan/not-detected.nix")
    (modulesPath + "/profiles/qemu-guest.nix")
    ./disk-config.nix
  ];
  boot.loader.grub = {
    efiSupport = true;
    efiInstallAsRemovable = true;
  };
  services.openssh.enable = true;

  environment.systemPackages = map lib.lowPrio [
    pkgs.curl # define your more packages here
    pkgs.gitMinimal
  ];

  users.users.root.openssh.authorizedKeys.keys = [
    "You're public ssh key"
  ];

  system.stateVersion = "23.11";
}

Now regarding the disk, I'm using the default disk partitions required and recommended by disko, If you happen to change you can. This disk partions are for bios compatible gpt partition.

  • disk-config.nix
# Example to create a bios compatible gpt partition
{ lib, ... }:
{
  disko.devices = {
    disk.disk1 = {
      device = lib.mkDefault "/dev/sda";
      type = "disk";
      content = {
        type = "gpt";
        partitions = {
          boot = {
            name = "boot";
            size = "1M";
            type = "EF02";
          };
          esp = {
            name = "ESP";
            size = "500M";
            type = "EF00";
            content = {
              type = "filesystem";
              format = "vfat";
              mountpoint = "/boot";
            };
          };
          root = {
            name = "root";
            size = "100%";
            content = {
              type = "lvm_pv";
              vg = "pool";
            };
          };
        };
      };
    };
    lvm_vg = {
      pool = {
        type = "lvm_vg";
        lvs = {
          root = {
            size = "100%FREE";
            content = {
              type = "filesystem";
              format = "ext4";
              mountpoint = "/";
              mountOptions = [
                "defaults"
              ];
            };
          };
        };
      };
    };
  };
}

Now, we'll have to run our flake to install nix on the vps.

 nix run github:nix-community/nixos-anywhere -- --flake .#wolf root@<server-ip>
Screenshot 2024-07-02 at 12 51 42
It'll ask for password to install the base of nix.
Screenshot 2024-07-02 at 12 55 31

And now, you should be able to log-in into the vps using the ssh keys , regardless of the password being empty.

Screenshot 2024-07-02 at 12 53 43

Setting Up Google Authentication for SSH on a Linux Server

Step 1: Install Google Authenticator

  1. Update your package list:

    sudo apt update
    
  2. Install the Google Authenticator package:

    sudo apt install libpam-google-authenticator
    

Step 2: Configure Google Authenticator for Your User

  1. Run the Google Authenticator setup:

    google-authenticator
    
  2. Answer the prompts:

    • "Do you want authentication tokens to be time-based (y/n)?": Type y and press Enter.
    • Backup Codes: Write down the emergency scratch codes provided and store them in a safe place.
    • "Do you want me to update your "/home/username/.google_authenticator" file?": Type y and press Enter.
    • "Do you want to disallow multiple uses of the same authentication token? (y/n)": Type y and press Enter.
    • "By default, tokens are good for 30 seconds. Do you want to increase the time skew window to 4 minutes? (y/n)": Type n and press Enter.
    • "Do you want to enable rate-limiting protection? (y/n)": Type y and press Enter.
  3. Scan the QR code: Use the Google Authenticator app on your phone to scan the QR code displayed on your terminal.

Step 3: Configure SSH to Use Google Authenticator

  1. Edit the SSH configuration file:

    sudo vim /etc/pam.d/sshd
    
  2. Add the following line at the end of the file:

    auth required pam_google_authenticator.so nullok
    
  3. Edit the SSH daemon configuration file:

    sudo vim /etc/ssh/sshd_config
    
  4. Find and modify the following lines:

    • Ensure ChallengeResponseAuthentication is set to yes:

      ChallengeResponseAuthentication yes
      
    • Ensure UsePAM is set to yes:

      UsePAM yes
      
    • (Optional) If you want to require both Google Authenticator and your password, add or modify the line:

      AuthenticationMethods publickey,password publickey,keyboard-interactive
      

Step 4: Restart the SSH Service

  1. Restart the SSH service to apply the changes:

    sudo systemctl restart sshd
    

Step 5: Test the Configuration

  1. Open a new SSH session to your server.

  2. Log in with your username and password.

  3. When prompted, enter the verification code from your Google Authenticator app.

Installing an SSL Certificate on a Domain Using Certbot manually

1. Install Certbot

If Certbot is not already installed, you can install it using the following commands:

For Debian/Ubuntu:

sudo apt update
sudo apt install certbot

2. Obtain the SSL Certificate

export DOMAIN=<your domain>

certbot certonly --manual -d *.$DOMAIN -d $DOMAIN --agree-tos --manual-public-ip-logging-ok --preferred-challenges dns-01 --server https://acme-v02.api.letsencrypt.org/directory --register-unsafely-without-email --rsa-key-size 4096

3. Certbot will provide instructions on how to create a DNS TXT record to verify your domain ownership. The output will look something like this:

Please deploy a DNS TXT record under the name
_acme-challenge.example.com with the following value:

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Before continuing, verify the record is deployed.

After creating the DNS TXT record, wait for the changes to propagate. This can take a few minutes. You can verify the DNS record by using a tool like DNSCheckerto ensure it has been properly set.

4. Setup ssl

Your new SSL certificates will be stored in the /etc/letsencrypt/live/$DOMAIN/ directory. You will find the following files:

server {
    listen 443 ssl;
    server_name example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        root /var/www/html;
        index index.html index.htm;
    }
}

Migrating from Jenkins to GitHub Actions for CI/CD

Introduction

Jenkins has been a go-to tool for CI/CD for many developers. However, it can be heavy and complex for smaller projects or when simplicity is desired. In this blog post, I will walk you through my experience migrating from Jenkins to GitHub Actions for CI/CD, highlighting the reasons for the switch and the steps taken to implement a new workflow.

Prerequisites

Before you start the migration, ensure you have the following:

  • A GitHub repository for your project.
  • Access to your VPS (Virtual Private Server) where the application will be deployed.
  • SSH keys generated and configured for your VPS.
  • GitHub secrets configured for storing sensitive information like SSH keys.

Why Migrate from Jenkins to GitHub Actions?

  • Complexity and Maintenance: Jenkins requires managing a separate server and handling its updates and maintenance, which can be time-consuming.
  • Performance: Jenkins can be resource-intensive, which might be overkill for smaller projects.
  • Integration: GitHub Actions provides seamless integration with GitHub repositories, making it easier to manage workflows within the same platform.
  • Simplicity: GitHub Actions offers a straightforward YAML configuration, which is easier to read and manage compared to Jenkins' XML-based configurations.

being small but absolute working workaround works as follows.

  • This workflow run on push on main branch and deploys in github and then only in vps.

In this guide, we'll be using appleboy/ssh-action, which lets you ssh into the server and run particular script.

For this workflow, we'll first need to have our ssh keys in our vps and the user must be able to login via ssh.

You'll want to generate ssh keys and then put your public keys in authorized_keys, which simply lets the current user ssh into the sever.

Setting Up the GitHub Actions Workflow

After generating ssh keys, put your:

  • PRIVATE_KEY, USERNAME, and HOST in your repository's secrets variables.

Here’s the GitHub Actions workflow that I set up to build and deploy my web application to a VPS:

name: Test the web app and deploy on vps

on:
  push:
    branches:
      - main

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v2

      - name: Install Nix package manager
        uses: DeterminateSystems/nix-installer-action@main

      - name: Install Node.js and pnpm
        run: |
          nix profile install nixpkgs#nodejs_22
          nix profile install nixpkgs#nodePackages_latest.pnpm

      - name: Build website
        run: |
          pnpm install
          pnpm run build

  deploy:
    runs-on: ubuntu-latest
    needs: build
    steps:
      - name: Deploy over the vps
        uses: appleboy/ssh-action@v1.0.3
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.PRIVATE_KEY }}
          port: 22
          script: |
            cd ~/source_dir/
            bash ./.github/deployer.sh

A graph of this workflow

The deployer script

The following script is ran by github actions on github build success

#!/usr/bin/env bash

# Written by @pwnwriter
# This script builds the Nest Nepal website. If the build fails, it attempts to build from the previous commit.

### Variables

USER="ubuntu"
REPO_DIR="/home/${USER}/repo_name"
INFO_DIR="/home/${USER}/repo_name-log"
LOG_FILE="$INFO_DIR/build.log"
SERVER_LOG="$INFO_DIR/server.log"
PORT=3002

# Color codes
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# -------------------- Helper functions ---------

# Appends log messages
append_log() {
    printf '%s - %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" | tee -a "$LOG_FILE"
}

# Prints informational messages
print_info() {
    local message="${1}"
    printf "${YELLOW}[INFO]${NC} %s\n" "$message"
    append_log "[INFO] $message"
}

# Prints success messages
print_success() {
    local message="${1}"
    printf "${GREEN}[SUCCESS]${NC} %s\n" "$message"
    append_log "[SUCCESS] $message"
}

# Prints error messages
print_error() {
    local message="${1}"
    printf "${RED}[ERROR]${NC} %s\n" "$message"
    append_log "[ERROR] $message"
}

# Creates log directories
create_log_dirs() {
    print_info "Creating log directories"
    mkdir -p "$INFO_DIR" || {
        print_error "Failed to create log directory"
        exit 1
    }
}

# Kills the current running process $port
kill_existing() {
    local pid
    pid=$(ss -lptn "sport = :$PORT" | grep -oP '(?<=pid=)\d+')
    if [ -n "$pid" ]; then
        print_info "Killing existing process with PID $pid"
        kill -9 "$pid" || {
            print_error "Failed to kill process with PID $pid"
            exit 1
        }
    else
        print_info "No existing process found on port $PORT"
    fi
}

# Builds the website
website_build() {
    print_info "Installing required modules"
    pnpm install 2>&1 | tee -a "$LOG_FILE" || {
        print_error "Failed to install modules"
        return 1
    }

    print_info "Building website"
    pnpm run build 2>&1 | tee -a "$LOG_FILE"
    if [ $? -eq 0 ]; then
        print_success "Build successful, starting website"
        kill_existing
        nohup pnpm start >>"$SERVER_LOG" 2>&1 &
        print_success "Website is live and running"
    else
        print_error "Build failed"
        return 1
    fi
}

# Main function
main() {
    cd "$REPO_DIR" || {
        print_error "Unable to change directory to $REPO_DIR"
        exit 1
    }

    print_info "Pulling latest changes from the main branch"
    git pull origin main --rebase || {
        print_error "Failed to pull latest changes from main branch"
        exit 1
    }

    create_log_dirs
    website_build

    if [ $? -ne 0 ]; then
        print_info "Build failed, attempting to build from previous commit"
        print_info "Reverting to previous commit"
        git checkout HEAD~ || {
            print_error "Failed to revert to previous commit"
            exit 1
        }
        website_build || {
            print_error "Build from previous commit failed"
            exit 1
        }
    fi
}

# Execute
main

Mount a directory on RAM.

Mount a directory completely on RAM. Everything will be wiped out on reboot.

mount -o size=<x>G -t tmpfs none <dir_name>

Explanation

size=<x>G: Sets the directory size in RAM (replace with GB, e.g., size=4G for a 4GB virtual directory).

-t tmpfs: Specifies tmpfs as the RAM-based filesystem.

none: Represents the virtual directory in RAM, not tied to any physical device.

<dir_name>: Path to the directory where you'll access the RAM directory.

Create SSH Keys

ssh-keygen -t rsa -b 4096 -C "example@example.com"

Generate a new SSH key using RSA encryption with a bit size of 4096 and attach an email address as a label.

Start SSH Agent

eval $(ssh-agent -s)

initialize ssh keys.

Add Generated Key to SSH Agent

ssh-add "~/.ssh/ssh_file_name"

Add newly generated SSH key to the SSH agent.

Some useful commands for enumerating *nix os

CommandsExplanation
uname -aPrint all available system information
uname -rKernel release
uname -nSystem hostname
hostnameAs above
uname -mLinux kernel architecture (32 or 64 bit)
cat /proc/versionKernel information
cat /etc/*-releaseDistribution information
cat /etc/issueAs above
cat /proc/cpuinfoCPU information
df -aFile system information

Users & Groups:

CommandsExplanation
cat /etc/passwdList all users on the system
cat /etc/groupList all groups on the system
cat /etc/shadowShow user hashes – Privileged command
grep -v -E "^#" /etc/passwd | awk -F: '$3 == 0 { print $1}List all super user accounts
fingerUsers currently logged in
pinkyAs above
usersAs above
who -aAs above
wWho is currently logged in and what they’re doing
lastListing of last logged on users
lastlogInformation on when all users last logged in
lastlog –u %username%Information on when the specified user last logged in

User & Privilege Information:

CommandsExplanation
whoamiCurrent username
idCurrent user information
cat /etc/sudoersWho’s allowed to do what as root – Privileged command
sudo -lCan the current user perform anything as root

Environmental Information:

CommandsExplanation
envDisplay environmental variables
setAs above
echo $PATHPath information
historyDisplays command history of current user
pwdPrint working directory, i.e. ‘where am I’
cat /etc/profileDisplay default system variables

Interesting Files:

CommandsExplanation
find / -perm -4000 -type f 2>/dev/nullFind SUID files
find / -uid 0 -perm -4000 -type f 2>/dev/nullFind SUID files owned by root

Common Shell Escape Sequences:

CommandsExplanation
:!bashvi, vim
:set shell=/bin/bash :shellvi, vim
!bashman, more, less
find / -exec /usr/bin/awk 'BEGIN {system("/bin/bash")}' \;find
awk 'BEGIN {system("/bin/bash")}'awk
--interactivenmap
perl -e 'exec "/bin/bash";'Perl

Random scripts

Record with internal sound using wf-recorder

wf-recorder \
-aalsa_input.pci-0000_03_00.6.analog-stereo \
-aalsa_output.pci-0000_03_00.6.analog-stereo.monitor \
-f recording.mkv