Skip to content

Category: Web Security

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.

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.

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.

Purpose of DevSecOps and its future

“…make sure that the constraint is not allowed to waste any time. Ever. It should never be waiting on other resource for anything, and it should always be working on the highest priority commitment the IT Operations organization has made to the rest of the enterprise. Always.”

The Phoenix project

The NUMBER ONE constraint in Security department is people.

It is unlikely we can hire enough people to match the number of developers and operations engineers.

The way to free up our constraint (people) is to try to automate as many tasks as possible so that the people can do the things that are unique and contextual.

Another way is to take a preventative approach by educating developers and ops engineers on best security practices that they need to follow (this means secure by defaults configurations, having documentation and guides). The famous Netflix’s paved roads….

any improvement not made at the constraint is just an illusion, yes?

The Phoenix project

Crafting your AppSec Learning Methodology

Information Security is a broad field with many specialties such as Infrastructure Security, Cloud Security, Container Security. DevOps automation and Application Security (Source Code / Design level) etc. In real life context, an Application Security may need to focus on a few specialties since modern applications are using Cloud PaaS / IaaS and Containers.

Since there are so many specialties to learn, you may get overwhelmed by the knowledge and how to apply what you learn on the actual context. I read an interesting Div0 post on a cybersecurity career discussion panel on this topic:

Many aspiring cybersecurity practitioners say they are passionate about cybersecurity. But they have no work to show/prove their passion. Don’t just say you are passionate. Do it, show it!

There’s no shortage of aspiring cybersecurity practitioners who want to learn. However, there’s a lack of drive on how they can contribute to the organization. Continuous learning in a fast-moving industry like cybersecurity is important. However, you should also focus on how you can apply your skills (emphasis mine).

Cybersecurity is not just about technical skills. In fact, picking up technical skills is the easy bit. Shaping your attitude and mindset in how you deliver the promise your clients entrusted in you plays a much bigger role.

An essential characteristic to have in the industry is Grit — the passion and sustained persistence towards long-term achievement.

In a wide discipline like cybersecurity, it is important to collaborate and bounce ideas with fellow practitioners — that’s how you push the boundary.

The mad rush to get all the fancy certifications is not healthy. There’s no point being certified but you still can’t get a complete grasp on security controls or even the fundamentals. Focus on building up your skillset, not the alphabets behind your name.

Often Application Security new joiners have to learn development skills on their own projects if they are not joining from development background. They may be pentesters, sysadmin, DevOps engineers or career changers coming from another field. Each of them have their own unique perspectives.

You need to understand development but…

If you see in many Application Security job descriptions, many companies require development background. This requirement ensures that you can empathize with the development team that you will be working closely with.

Be careful not to become too developer-like because you will lose independent assessment since you are too close to the development team. For example, some developer tells you that this SQL injection is not a risk since the string values come from hardcoded config file. If you get too close to dev team, you might accept the risk without pointing out additional caution.

However, from another independent perspective, this means that SQL injection can happen if a rogue developer / QA / devops change the config file to a SQLi payload. The long-term solution is actually to use parameterized query and prepared statement even though the values are hardcoded. However if you get too close to dev team, you will start to feel their pain and deadlines if the feature is not released on time. So you might not even suggest the team to pick a more secure approach. Or you might ask the team to add this suggestion to backlog. It remains to be seen whether the safer approach will be adopted since there is always another new feature to complete.

Security Champions might also struggle to give independent assessment especially since they are part of the dev team. Moreover, many of them are developers or QA and have deadline and user stories to complete as well. Therefore Security Champions cannot do it alone and need pairing with the internal Security SME to give a balanced assessment.

On the other hand, you are too far from the development team, you will start suggesting crazy security activities from some PDFs or slides that will never gain any traction with the development team. Many good ideas sound great in powerpoint slides but difficult to gain traction in practice because real world has more decision-making factors in which security is just one of them. You need to at least understand some of these major factors such as business risk appetite, ROI, regulatory requirements etc.

Not enough to learn Development

To understand Application Security in depth, the newbies have to pick up coding and software development concepts such as MVC etc. on their side projects or on the job. It will take time and requires a lot of experimentation, readings, note-taking and questioning.

You shouldn’t just learn coding like watching a Udemy course on Web Development or Mobile Development. After all, your job also consist of the security aspects of delivery. Most of the development courses will not mention Application security topics. This means that you need to create your own contexts to apply every topics that you have watched on Application Security.

An example of I apply newly learned security knowledge to coding

Recently, I attended an online webinar on building secure React applications. This is an informative talk with hands-on examples that I recommend people to watch.

My first thought is to find the React code that I wrote on my own.

import React from 'react';

const Option = (props) => {
    return (
                onClick={(e) => {

export default Option;

Previously, my understanding was that ReactJS is secure by default library and XSS is difficult to achieve unless developer use dangerouslySetInnerHTML. But from the video, I updated my understanding of ReactJS and recognized more XSS attack surface.

Then I tried to modify my React component code to create the XSS vulnerability. Imagine the developer thinks that getting the name from props is safe to use in href.

import React from 'react';

const Option = (props) => {
    return (
            <a href={}>Click for more info</a>
                onClick={(e) => {

export default Option;

But if the attacker include JavaScript url, then there is a stored XSS issue.

From now, when I look at ReactJS code, I will also include such checks in my code review methodology. Or when I am looking at ReactJS app, I will be more awareness of more things to look out for such as href and src tags etc.

Testing for SSRF during PDF Generation

How to test for SSRF during PDF Generation?

User input is reflected in the PDF.

HTML elements are parsed by the PDF Generator Libraries.

Research the specific types of data that can be parsed by the target’s PDF Generator Library in order to generate a payload


If HTML is parsed directly:



Redirect with iframe



<iframe src="">


<iframe src="file:///etc/passwd">

WeasyPrint PDF Test

Trying to embed a secret file in the PDF? You can try this payload if the target is parsing a HTML page. One thing that you can do is host the index html file in Heroku etc. and pass it to the target’s PDF generation endpoint.

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <title>WeasyPrint PDF Test</title>
    <link rel=attachment href="file:///{path_to_secret_file}">
    <h1>WeasyPrint PDF Test</h1>

In the PDF reader, open up the Attachment section and view the embedded file.


Notes on Blind SQL Injection

Lab: Blind SQL injection with conditional responses

In this lab, we are using the responses to enumerate the password of the “administrator” account. First, we need to perform a check on whether the “administrator” account exists and the length of the password. Once this is done, we will perform a substring query to enumerate each of the character of the password.

You can use Burp Repeater or Intruder to enumerate the password. I find both methods to be time-consuming. I wrote a script to enumerate the password instead. The script will look for “Welcome” value in the response. If it is true, the script will note down the character and continue to the next position until all the password character is enumerated.