Skip to content

Author: Bobby Lin

Gitea: Configuration Hardening

Below are some of the configurations of Gitea that are useful for security hardening:

Config Reference: https://docs.gitea.io/en-us/config-cheat-sheet/

The more features we disable, the less attack surfaces we will face when using Gitea.

version: "3"

networks:
  gitea:
    external: false

volumes:
  gitea-data:
    driver: local
  gitea-config:
    driver: local

services:
  server:
    image: gitea/gitea@sha256:ef6279e13e223d956bc417f299a3530795afcf79f3af429a5c893262c550a648
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__repository__DISABLE_DOWNLOAD_SOURCE_ARCHIVES=true
      - GITEA__repository__DISABLE_HTTP_GIT=true
      - GITEA__repository__repository.upload__ENABLED=false
      - GITEA__server__DISABLE_SSH=true
      - GITEA__security__DISABLE_GIT_HOOKS=true
      - GITEA__security__DISABLE_WEBHOOKS=true
      - GITEA__security__PASSWORD_COMPLEXITY=on
      - GITEA__security__MIN_PASSWORD_LENGTH=10
      - GITEA__attachment__ENABLED=false
      - GITEA__api__ENABLE_SWAGGER=false
      - GITEA__packages__ENABLED=false
      - GITEA__migrations__ALLOWED_DOMAINS=github.com,*.github.com
      - GITEA__repository__MAX_CREATION_LIMIT=0
      - GITEA__security__PASSWORD_HASH_ALGO=argon2
      - GITEA__mirror__DISABLE_NEW_PUSH=true
      - GITEA__service__DISABLE_REGISTRATION=true
      - GITEA__repository__DISABLED_REPO_UNITS=repo.issues, repo.ext_issues, repo.pulls, repo.wiki, repo.ext_wiki, repo.projects
      - GITEA__service__DEFAULT_ALLOW_CREATE_ORGANIZATION=false
      - GITEA__service__DEFAULT_USER_IS_RESTRICTED=true
      - GITEA__repository__DISABLE_MIGRATIONS=true # Set as false when there is new mirror
      - GITEA__mirror__DISABLE_NEW_PULL=true # Set as false when there is new mirror
    restart: always
    networks:
      - gitea
    volumes:
      - gitea-data:/var/lib/gitea
      - gitea-config:/etc/gitea
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"

How to integrate Gitea with Okta (Authentication)?

Start up Gitea

Create a docker.compose.yml file with the latest Gitea rootless image:

version: "3"

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea@sha256:ef6279e13e223d956bc417f299a3530795afcf79f3af429a5c893262c550a648
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "222:22"

Run a command to start Gitea as a docker image in your machine:

docker-compose --project-name gitea -f docker.compose.yml up -d
docker ps -a # Check if the containers are running

Go to localhost and you can see that a local instance of Gitea is running.

Okta integration

Create a Web app in Okta:

Configure the redirect URL: http://localhost:3000/user/oauth2/okta/callback

Save the Client Id and Client secret that will be input into Gitea

You must name the authentication to be the same name under the redirect url. In this case, I have name it as okta

http://localhost:3000/user/oauth2/okta/callback

The admin should paste the Client Id and Client secrets from Okta app to the Authentication Sources tab in “Site Administration”.

Test the integration between Gitea and Okta

Sign in with OpenID

You will be redirected to Okta for authentication (ensure MFA is enabled preferably with FIDO2)

If this is the first time that you login with Gitea, you will need to fill in your username and email address to create an account.

GH Actions Misconfiguration – causing Command Injection

Suppose you have a Github Actions script like below:

The script will be triggered when a new issue comment is added to the issue or a comment is edited.

name: GitHub Actions Demo
on:
  issue_comment:
    types: [created, edited]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - run: echo ${{ github.event.comment.body }}

Notice that ${{ github.event.comment.body }} is used in the run step directly? This makes the script vulnerable to Command Injection. Why?

Read this: https://securitylab.github.com/research/github-actions-untrusted-input/#remediation

In this context, someone can inject commands to the GH Action script by adding their payload to the issue comment. The GH Action script is triggered every time there is a new issue comment.

Did you notice we can run echo bye? This is just a simple example but worse scenario can be reverse-shell (if you are using self-hosted GH Runners) or secrets theft.

See that we can echo bye

You can even attack in a stealth mode by deleting the comment immediately once the workflow is triggered.

How to prevent the issue?

Set the github context data in env variable before you use it in the run

[...]
      - env: # Add this
          BODY: ${{ github.event.comment.body }} # Add this
        run: echo "$BODY"

Add a new comment that contains a payload:

You can see the script is interpreting the comment body as string rather than interpolating it as a command to execute.

Book Review: Art of Impossible by Steven Kotler

Book Link: https://www.amazon.com/Art-Impossible-Peak-Performance-Primer/dp/0062977539

Flow Triggers

  • Intrinsic Drivers (Curiosity, Passions and Purpose [Massively Transformative Purposes])
  • Autonomy (Freedom to do)
  • Mastery (Challenge-Skills improvement)

Curiosity

  • List down 25 items that you are curious about. For example, you will spend your free time on researching a topic or problem, read a book on a subject, attending a lecture, watching a youtube video and keeping track of the topic in news or social media like Twitter.
  • Make sure that the listed items are specific enough so that your brain can look for patterns. If the topic is too broad (e.g. Application Security), then it will be hard to discover what exactly you want to explore.
  • Hunt for the curiosities intersections. Discover the overlapping areas between the different items that you have listed.
  • Curiosity by itself is not enough to trigger any motivation. You need to stack one curiosity on top on another. The more you stack, the more powerful the list of stacked curiosities will be to motivate you to research and learn more.
  • Whenever you recognize a new pattern between what you are curious about, your brain reward you with Dopamine.
    • Helps you to focus on the task
    • Detect more patterns by improving signal-to-noise ratio
    • Makes us feel good about doing an activity.
    • Enhance our memory on a specific subject.
  • Spend time on these intersections. Allocate 20 to 30 minutes daily on listening to podcasts, watching videos, reading articles, books, lectures, doing practical projects related to any aspects of the intersections.
    • For example, if you are interested in how solving Leetcode problems can help to improve your programming skills. You might try to read a few pages on a Data Structure and Algorithm (DSA) topics. Then try to solve 1 related question to the topic that you are reading. Or you might read a specific book on the programming language such as Fluent Python and see how you can write better code.
  • Engage in these curiosities everyday. This will allow your brain to adapt and process the new information in the new subject slowly. This method is similar to “incubation” stage where your brain is joining the old information with the new information to form more patterns. Let our brain naturally make these connections without forcing our brain to make any discoveries.
  • Pay attention to these two things while you are engaging in your curiosities:
    • History of the subject. If you note down the historical details of the subject, your brain will create a narrative that help you to snitch the details into a coherent story that you can remember easily without effortful memorization.
    • Technical Language. There are precise definitions for jargons in the field that you need to understand in order to make better connections between what you are exploring. To communicate and understand the subject better, you need to note down the jargons definitions. The experts in the field use the jargons to explain something precisely.
  • Go Public. Before going public, make sure you spend time playing around the intersections of your curiosities. At least have some unique perspectives and ideas before going to public (online forum, book clubs, meetups etc.)

Purposes

  • Write down your massively transformative purpose (MTPs). The purpose (reason why the work is done) that is large and audacious, and bring significant change to an industry, community or to the planet.
  • Look for areas where your core passions intersect with the MTPs. You want to look for the overlap between passion and purpose.

Personal Notes:

Although the book presented like an algorithmic process for triggering flow that help to accomplish the daily tasks, the process is not so linear. You might not know what are your MTP (massively transformative purposes) while reading the book. This discovery of your MTP might take time and explorations of your curiosity.

How do you even have 25 list of items that you are curious about? It can be things that you learn from books, online courses, youtube videos, internet forums, social media or from friends etc. Everytime you consume the information, take note of what you are curious about and the questions that you want to ask further on a specific area.

Reading Git Blob objects in Java

In order to read the Git Blob objects, we need to understand that git uses zlib to compress the stored objects. We can use the Java zip utils to decompress the Git blob.

Code Snippets

Below are some methods of decompressing the Blob file. If you did some research online, you will find many examples showing Method 1.

But I recommend using Method 2 as it is does not assume the size of the decompressed file. This method is also used in Apache Commons library.

Make sure the binary file is not corrupted or you might encounter java.util.zip.ZipException

Method 1

The byte array size of result can be arbitrarily set with a specific size. But you will have problems if the decompressed file size is uncertain.

String file = "<PATH to Git blob>";
byte[] fileBytes = Files.readAllBytes(Paths.get(file));
Inflater decompresser = new Inflater();
decompresser.setInput(fileBytes, 0, fileBytes.length);

byte[] result = new byte[1024]; // Size need to be set

int resultLength = 0;
resultLength = decompresser.inflate(result);

decompresser.end();

Method 2: Checks if end of compressed data is reached

This method reads the content of the file and output to ByteArrayOutputStream object.

String file = "<PATH to Git blob>";
byte[] fileBytes = Files.readAllBytes(Paths.get(file));

Inflater inflater = new Inflater();
inflater.setInput(fileBytes);

ByteArrayOutputStream outputStream = new ByteArrayOutputStream(fileBytes.length);
byte[] buffer = new byte[1024];

while (!inflater.finished()) {
  int count = inflater.inflate(buffer);
  outputStream.write(buffer, 0, count);
}

outputStream.close();

byte[] result = outputStream.toByteArray();

References

Notes on Finding Evasive Bugs (by James Kettle)

Highly recommend security researchers to watch this. The talk focuses more toward improving your methodology and mindset:

  1. Don’t look for defences; Start with the attacks first.
  2. Look for unfashionable flaws.
  3. Your understanding of a vulnerability concept may be corrupted. Don’t reply on internet searches to learn. Learn from the original sources.
  4. Recognise the fear from the new or unknown (Technique sounds cool but…)
  5. Recognise that you might think that something is an implausible idea. Don’t just try something and then give out if it does not work. Instead do this: Explain why the idea will not work unless condition X exists. Try the obvious to make sure that it is obviously secured.
  6. Invisible Chainlinks give you advantages. They can be related to a particular context, application specific knowledge, inconvenient. For example, param miner works well if you have the application specific knowledge.

Automation

  • Use automation to scan for clues about the application.
  • Scan to learn
    • Test Hypothesis
    • Ask question and iterate
  • When enumerating, focus on specific things rather than broad information to reduce noise.
    • Make asking questions cheap.
    • Develop your own framework.

Using snap in Kali Linux to install tools

Snap is a nice tool manager to install tools such as IDEs quickly without hassle.

https://snapcraft.io/

Installing snap in Kali

Run these command to install snap.

$ sudo apt update
$ sudo apt install snapd
$ systemctl enable --now snapd apparmor

If you encountered below error, this is because snapd.service is not started yet.

snap install hello-world
error: cannot communicate with server: Post "http://localhost/v2/snaps/hello-world": dial unix /run/snapd.socket: connect: no such file or directory

Run the following command and you should be able to see the version

$ systemctl enable --now snapd apparmor
$ snap version                                                                                                                                                                        127 ⨯
snap    2.55.5
snapd   2.55.5
series  16
kali    2021.1
kernel  5.10.0-kali3-amd64

Installing and Running Pycharm

According to https://www.jetbrains.com/pycharm/download/#section=linux, we can install pycharm in Kali Linux using snap.

$ sudo snap install pycharm-community --classic

To run Pycharm, simply use snap run:

snap run pycharm-community

References

  • https://snapcraft.io/docs/installing-snap-on-kali

Walkthrough – Solidity Smart Lottery project

The source of the project is from:

https://github.com/PatrickAlphaC/smartcontract-lottery
https://www.youtube.com/watch?v=M576WGiDBdQ&t=22298s

The purpose of this walkthrough is to explain each part of the Smart Contract code with more context so that we can gain better understanding.

Lottery.sol

Think about the different processes for a lottery.

  1. Participants have to enter the lottery. We need to track down who are the participants for this lottery.
  2. We need to know how much to charge the participants for entering the lottery.
  3. Owner will need to start the lottery.
  4. Owner will end the lottery and we need a random way to calculate the winner of the lottery. And we need a robust way of generating the random winner that minimise the probability of participants cheating.
  5. We need to track the state of the lottery: whether it is opened, closed or the winner is being calculated.

Tracking Participants

We need to create an address array to track down the participants. Before we add the new player to the participant list, they have to pay an entry fee. This entry fee is calculated based on current Ether price.

address payable[] public players;

function enter() public payable {
    require(msg.value > getEntranceFee());
    players.push(msg.sender);
}

We can use ChainLink price feed to get the latest Ether data. To do so, we need to specify the dependencies and remappings in brownie-config.yaml file.

import "@chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";

[...]
AggregatorV3Interface internal ethUsdPriceFeed;

function getEntranceFee() public view returns (uint256) {
    (, int256 price, , , ) = ethUsdPriceFeed.latestRoundData();
    uint256 adjustedPrice = uint256(price) * 10**10;
    uint256 costToEnter = (usdEntryFee * 10**18) / adjustedPrice;
    return costToEnter;
}

Walkthrough – Solidity Local Development setup (with Python)

Requirements

  • Install python3 and the following python packages:
pip install web3 py-solc-x
  • VS code IDE and extensions: Solidity (Juan Blanco), Python (Microsoft)

We are going to walkthrough a tutorial created by Patrick Collins (https://github.com/PatrickAlphaC/web3_py_simple_storage)

  • Install nodejs
  • Install ganache using yarn or npm (to simulate a local blockchain VM)

Setting and Deploying the Smart Contract

Create a new project folder and copy the sample solidity code to a new file SimpleStorage.sol:

// SPDX-License-Identifier: MIT

pragma solidity >=0.6.0 <0.9.0;

contract SimpleStorage {
    uint256 favoriteNumber;

    // This is a comment!
    struct People {
        uint256 favoriteNumber;
        string name;
    }

    People[] public people;
    mapping(string => uint256) public nameToFavoriteNumber;

    function store(uint256 _favoriteNumber) public {
        favoriteNumber = _favoriteNumber;
    }

    function retrieve() public view returns (uint256) {
        return favoriteNumber;
    }

    function addPerson(string memory _name, uint256 _favoriteNumber) public {
        people.push(People(_favoriteNumber, _name));
        nameToFavoriteNumber[_name] = _favoriteNumber;
    }
}

Create a deploy.py file. We are going to walkthrough step-by-step to understand each part of the code.

1. Import these required modules.

from solcx import compile_standard
from solcx import install_solc
import json
import os
from web3 import Web3

2. We use solcx to compile the smart contract code. This is a python wrapper for the Solidity compiler.

First we open and read the content of the smart contract code.

Then install the solidity compiler version. It will download the compiler from the project’s Github download link.

Configure the compile standards:

  • language
  • sources
  • settings
  • solidity compiler version

We can dump the compiled code to see the structure of the code.

with open("./SimpleStorage.sol", "r") as file:
    simple_storage_file = file.read()

print("Installing solc...")
install_solc('0.6.0')

# Configure compile standards
compiled_sol = compile_standard(
    {
        "language": "Solidity",
        "sources": {"SimpleStorage.sol": {"content": simple_storage_file}},
        "settings": {
            "outputSelection": {
                "*": {
                    "*": ["abi", "metadata", "evm.bytecode", "evm.bytecode.sourceMap"]
                }
            }
        }
    },
    solc_version="0.6.0"
)

# Dump the compiled code to see the structure of the code
with open("compiled_code.json", "w") as file:
    json.dump(compiled_sol, file)

3. Get the bytecode and abi from the compiled code. How do we know the structure of the JSON? Refer to the compiled_code.json file

bytecode = compiled_sol["contracts"]["SimpleStorage.sol"]["SimpleStorage"]["evm"]["bytecode"]["object"]

abi = compiled_sol["contracts"]["SimpleStorage.sol"]["SimpleStorage"]["abi"]

4. Now start ganache server and we can see the generated dummy accounts and private keys. Note down where the server is listening.

$ ganache
ganache v7.0.2 (@ganache/cli: 0.1.3, @ganache/core: 0.1.3)
Starting RPC server

Available Accounts
==================
(0) 0xB136383615B477B1B816f4227A509ea8F0C0c9DD (1000 ETH)
(1) 0xB6c6BDb34A834BAcc8e07c9765E2f85D1619beDc (1000 ETH)
(2) 0xb2e141ed4EF4F30BC7a1848FFbd623b19B08608C (1000 ETH)
(3) 0x32c0DB620E7355feE0254813932a4E7a454D74f8 (1000 ETH)
(4) 0xB19D507aEE3BdA3c9da9b360E491B88FFd857f14 (1000 ETH)
(5) 0x7B0929a005B39Cce2C9795558371F3865Cff1Bf9 (1000 ETH)
(6) 0x13665EC9cEE2915402BD7Ce69c05F70E9CBCF2ef (1000 ETH)
(7) 0x2C2c3C4585c9425969C03055554dD0C15f5a57b8 (1000 ETH)
(8) 0xdAD8Ae2871Cb242C001A68EB5Bc6941BFDB0d2A7 (1000 ETH)
(9) 0x85214561dCD632581a0b60BeE5989607005BE663 (1000 ETH)

Private Keys
==================
(0) 0xa4c6bac88b45ba1e21eafbd736c92ca60b67bbfb956ccd3da37fa6f83ebe38c1
(1) 0xfa197f239d6df371b0242b8fe96b0d1883a392ff5ab4502cefae0e972f07f081
(2) 0x6c96d2d7b0fb9b56cad49887e3f198de2faa93d321240201d0572732f83bbcc8
(3) 0xd9eb1ec171c1aee37b0603b85d58b615d678f3f9c85c4e5fe31c322113d009d3
(4) 0xd6c212925da4e19a4708b43d1728efef1cf5c839fd44ee411107837b30d8e38c
(5) 0x048c50d14357791a5cbf1ecbf1febda6bf215f946bac50fac410524cde6cb397
(6) 0xb9eeb83abac9b23a5bc801e584c646f92ecb37cd4b7770100f84ea2ccdf3a304
(7) 0xad157fbf68f3e7fb3047ca653c073b4389f5020ce8d397c7c8f3533c491a15ba
(8) 0x01caede47d8ad5d0e5c125b9d4ceba7abd201a26be10730fc926f673f275fd42
(9) 0x07da7e22ff6a56ce2c07bf724913e507621181f369d481fd94ebb12d577d4650

HD Wallet
==================
Mnemonic:      team shoot anchor limit inform imitate melody decrease wing sadness orange mammal
Base HD Path:  m/44'/60'/0'/0/{account_index}

Default Gas Price
==================
2000000000

BlockGas Limit
==================
30000000

Call Gas Limit
==================
50000000

Chain Id
==================
1337

RPC Listening on 127.0.0.1:8545

5. Setup connection to the ganache server


w3 = Web3(Web3.HTTPProvider("http://127.0.0.1:8545"))
chain_id = 1337
my_addr = "0xB136383615B477B1B816f4227A509ea8F0C0c9DD"
private_key = os.getenv("PRIVATE_KEY")

DO NOT HARDCODE YOUR PRIVATE KEY IN YOUR CODE REPOS

If we want to connect to Testnet environment, we can use an ETH gateway such as Infura or Alchemy.

6a. Create the contract with the provider using the abi and bytecode.

SimpleStorage = w3.eth.contract(abi=abi, bytecode=bytecode)

6b. Get latest transaction count and use it as the nonce

nonce = w3.eth.getTransactionCount(my_addr)

6c. Submit the transaction that deploys contract using the chainId, gasPrice, from (which addr) and nonce

transaction = SimpleStorage.constructor().buildTransaction(
    {
        "chainId": chain_id,
        "gasPrice": w3.eth.gas_price,
        "from": my_addr,
        "nonce": nonce
    }
)

6d. Sign the transaction with private key

signed_txn = w3.eth.account.sign_transaction(transaction, private_key=private_key)

7. Deploy the contract

tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction)tx_receipt = w3.eth.wait_for_transaction_receipt(tx_hash)contract_addr = tx_receipt.contractAddressprint(f"Contract is deployed to {contract_addr}")

Using the Smart Contract

Since the Smart Contract is deployed, we can provide the contract address and abi to create the smart contract (“simple_storage”).

simple_storage = w3.eth.contract(address=contract_addr, abi=abi)

We can make a function call without changing any state in the smart contract. In this example, we are trying to retrieve the stored value in the smart contract.

print(f"Initial Stored Value = {simple_storage.functions.retrieve().call()}")

Now, we call the store function in the smart contract to update favoriteNumber variable.

greeting_transaction = simple_storage.functions.store(15).buildTransaction(
    {
        "chainId": chain_id,
        "gasPrice": w3.eth.gas_price,
        "from": my_addr,
        "nonce": nonce + 1
    }
)

We will sign this transaction with the private key, send the transaction to the Ganache server and then wait for the transaction receipt.

Notice if you execute the transaction in local blockchain VM, the transaction speed will be very fast. But in actual Testnet or Mainnet, the transaction is likely to be slower.


signed_greeting_txn = w3.eth.account.sign_transaction(greeting_transaction, private_key=private_key)

tx_greeting_hash = w3.eth.send_raw_transaction(signed_greeting_txn.rawTransaction)

tx_receipt = w3.eth.wait_for_transaction_receipt(tx_greeting_hash)

Let’s print the stored value and we can see it is changed to 15

print(simple_storage.functions.retrieve().call())