Skip to content

Application Security Posts

Never stop learning -> To perform better

  • Spend time to dive into the development framework or security documentations that you are not familiar with.
  • Do it everyday consistently in an allocated time.
  • Understand the Framework hardening guideline. Study the framework security docs.

I just dedicate half an hour to  an hour every day to exercise   and to study a little bit, and I still get  my other work done. It’s a discipline, and  there’s no easy answer John. That’s the best. I think, unfortunately, you’re exactly correct. 

Jim Manico

Notes on “DevSecOps Worst Practice by Tanya Janca”

Jun 8, 2023
Presenter: Tanya Janca, CEO and Founder, We Hack Purple

Recently I watched a talk from Tanya Janca on DevSecOps worst practices. The talk is interesting because I have seen similar practices during the early adoption stage of DevSecOps processes and culture. If you find that your DevSecOps practices have plateaued, chances are that your program is practicing some of the bad practices.

Below is a list of bad practices mentioned by Tanya:

  1. Breaking Build on False positives
  2. Turning on a tool without testing
  3. Artificial Gates
  4. Missing Test Results (Results are not shared in a convenient location)
  5. Missing Runway Testing (Not testing the tools in the CI pipelines to see the job duration impact)
  6. Impossible SLAs
  7. Untrained Staffs
  8. Bug lost in Backlog
  9. No Positive Reinforcement
  10. Only worrying about your part (Not understanding whether another teams are facing any issues if you make the changes)
  11. Multiple Bug trackers (Bugs tracking are scattered in different dashboards)
  12. INSecure SDLC (Tools are not enough. We need more process and other activities like Threat Modelling)
  13. Overly Permissive CI/CD (Skipping a process to deploy to prod)
  14. Automation only in CI/CD
  15. Hiding Mistakes and errors

I will only highlight a few of these practices and share my thoughts.

Turning on a security tool without thorough testing

Testing a new tool for effectiveness is HARDER than we think. Usually POC on a vendor product has a limited timeframe (about one month plus). And you need to identify the teams that are suitable to test the products. If you chose the wrong team to test the product, you might jump into conclusion that this product is bad. Or if you test it on only subset of team, we still find it uncertain if the performance can be replicated to other teams who are using a different tech stack.

Tanya mentioned that one way we can do is buy perpetual license and trial it for about 6 months before deciding to purchase a larger contract. I think this is a good recommendation.

From vendor point of view, you should think about how you can let the security team try the important features of product and test it sufficiently so that they can make better conclusions.

Overly Permissive CI/CD

Tanya gave an example about how the development team can disable tests in order to deploy to production. If too much permission is given to the team, some might abuse the permissions to reach their goal.

In general, I think CI is a wild jungle where you can run any scripts or workflows. But the crucial thing is to restrict the permissions for deployment (CD). If we control the permissions better, we can stop people from abusing deployment rights.

Impossible SLAs

In some organizations, there are strict SLA requirements for deployment to production. For example, there can be a SLA that “NO CRITICAL or HIGH” issues in production.

This might be impossible to meet for some of the teams (Especially if they have legacy apps). Tanya recommends that Security team should consult other teams to see if this SLAs is feasible. Listen to see if the teams can fix the issues to meet the SLAs.

AWS Lambda (Python) – Preventing Command Injection

When writing a AWS lambda function, we need to be careful about secure coding issues.

If your lambda function takes an input and use it to run a command, you need to avoid using os.system(...). You should use but without the shell. The commands provided to subprocess should be in arguments.

Explaining the injection

I will use an example of AWS Lambda function that takes an input from event and uses it in a subprocess to run a command. Notice the command is used in a f-string.

import subprocess
import uuid

def lambda_handler(event, context):
    rand_value = uuid.uuid4().hex[:6]
    file_name = f'{event['file_name']}_{rand_value}'
    print('Creating:', file_name)

    result ='cd /tmp && touch {file_name} && ls -la', shell=True, stdout=subprocess.PIPE)

We can inject a malicious filename to steal the values in env

ubuntu@ubuntu-virtual-machine:~$ python3
Enter file name: hello && env | base64 #
Creating: hello && env | base64 #_526feb


We should refactor the vulnerable code by adding two defences:

Input validation

Dependings on the context, try to whitelist the allowed characters.

If you do not know in advanced then it is crucial that you follow the next defence of using arguments command for subprocess.

Usage of arguments in subprocess

Instead of providing the commands in concatenated string or string format, provide an argument format ['touch', file_name]. This means you need to break down the command in different `subprocess` calls. You cannot run the multiple commands in one single subprocess.

One common routine is that you need to run a sequence of command at a specific same directory. With the use of cwd argument in, you can specify where the command should be executed.

import subprocess
import uuid

def lambda_handler(event, context):
    rand_value = uuid.uuid4().hex[:6]
    file_name = f'{event['file_name']}_{rand_value}'
    print('Creating:', file_name)

    working_dir = '/tmp'['touch', file_name], cwd=working_dir, stdout=subprocess.PIPE)
    result =['ls', '-la'], cwd=working_dir, stdout=subprocess.PIPE)

Canary Token is great…but beware of a flaw when using Thinkst’s free service for Canary AWS token


Canary Token is a great idea for early detection of possible intrusion in your system.

But be careful when you are using the free service from Thinkst Canary ( to generate Canary AWS tokens. You shouldn’t use it for real life production.

A threat actor may notice a common pattern in the AWS access key id (generated from Thinkst Canary) and avoid triggering your honeypot detection.

AWS Access Key ID Format

Aidan Steele wrote an in-depth article on AWS Access Key ID format. Highly recommend to read it.

Things to note from Aidan’s article:

  • The first 4 chars of the AWS Access Key will be indicate the resource types (
  • The next 8 chars of the access key will be mapped to a specific AWS account.

We can get the AWS account id from the access key by using the aws-cli

$ aws sts get-access-key-info --access-key-id <<ACCESS_KEY_ID>> --query Account --no-cli-pager

Flaws from the free service

Let’s generate a few Canary tokens from

aws_access_key_id = AKIAYVP4CIPPxxxxxxxU
aws_secret_access_key = xxxxxxxx
output = json
region = us-east-2
aws_access_key_id = AKIAYVP4CIPPxxxxxxxW
aws_secret_access_key = xxxxxxxxx
output = json
region = us-east-2

As you can see from the two generated Canary token, the four chars of the access key id show that this is an access key. Then the next crucial pattern (next 8 chars) will expose the information that this access key belongs to the AWS account from the free Canary token service.

A smart threat actor will detect this pattern and avoid using it. As a result, your Canary Token honeypot will likely to fail.


  • Avoid using Canary Token services which provide predictable access key pattern. A threat actor can identify the pattern and avoid triggering the canary token.
  • Canary Token is one of the detectors that we should use, but beware of confirmation biases (Canary token not triggered does not mean there is no intrusion).

Gitea: Configuration Hardening

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

Config Reference:

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

version: "3"

    external: false

    driver: local
    driver: local

    image: gitea/gitea@sha256:ef6279e13e223d956bc417f299a3530795afcf79f3af429a5c893262c550a648
    container_name: gitea
      - USER_UID=1000
      - USER_GID=1000
      - 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__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.ext_wiki, repo.projects
      - 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
      - gitea
      - gitea-data:/var/lib/gitea
      - gitea-config:/etc/gitea
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - "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"

    external: false

    image: gitea/gitea@sha256:ef6279e13e223d956bc417f299a3530795afcf79f3af429a5c893262c550a648
    container_name: gitea
      - USER_UID=1000
      - USER_GID=1000
    restart: always
      - gitea
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
      - "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


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
    types: [created, edited]
    runs-on: ubuntu-latest
      - 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:

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:

Flow Triggers

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


  • 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.)


  • 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

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);


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();

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

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


byte[] result = outputStream.toByteArray();


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.


  • 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.