Pentesting with Selenium WebDriver

November 27, 2024

Introduction

Selenium combined with chromium is a powerful tool for pentesting web applications. In this post, we will explore how to use Selenium WebDriver with a custom chromium instance to scrape data and execute desired actions. NOTE: This post is for educational purposes only. Always follow the terms of service. 😇

Background

For this post, we will be looking at some code I wrote for pentesting dingdingding.com, a social casino website that offers a daily bonus to its users. The code authenticates in to the website, collects the daily bonus, and provides a countdown for the next. The full code is available here.

Authentication

This is the first step to access the website and complete actions. This is also the most challenging step, as we have to get past a 2Captcha challenge. The code below uses selenium for clicking the elements and sending the credentials needed to authenticate. You may be wondering why there is an 80 second asyncio.sleep statement at the beginning. This is because the we need our chrome extension to solve the 2captcha. To load the extension into the browser, we copy the .crx to a temp directory in the Dockerfile.

# Copy the .crx file to a temporary directory
COPY ./CAPTCHA-Solver-auto-hCAPTCHA-reCAPTCHA-freely-Chrome-Web-Store.crx /temp/CAPTCHA-Solver-auto-hCAPTCHA-reCAPTCHA-freely-Chrome-Web-Store.crx

This chrome extension uses “AI” to solve the captcha. I am fully aware that there are alternatives to solving 2captcha, but they rely on manual human labor at rates that are exploitative.

# Function to authenticate into DingDingDing
async def authenticate_dingdingding(driver, bot, ctx):
    global authenticated
    try:
        web = "https://www.dingdingding.com/login"
        driver.get(web)
        await asyncio.sleep(80)

        # Wait for email and password fields, enter credentials from environment variables
        email_field = WebDriverWait(driver, 30).until(
            EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div/div[1]/div[1]/div/div/div[2]/form/input[1]"))
        )
        email_field.send_keys(os.getenv("DINGDINGDING").split(":")[0])

        password_field = WebDriverWait(driver, 30).until(
            EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div/div[1]/div[1]/div/div/div[2]/form/input[2]"))
        )
        password_field.send_keys(os.getenv("DINGDINGDING").split(":")[1])
        
        await asyncio.sleep(3)
        try:
        # Click login button
            login_btn = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.XPATH, "/html/body/div[1]/div/div/div[1]/div[1]/div/div/div[2]/form/button[2]"))
            )
            login_btn.click()
        except:
            await ctx.send("Unable to solve captcha. Try again later.")
            return False
        await asyncio.sleep(5)

        # Check if login was successful
        if driver.current_url == "https://dingdingding.com/lobby/":
            await ctx.send("Authenticated into DingDingDing successfully!")
            authenticated = True
        else:
            await ctx.send("Authentication failed. Did not reach the lobby.")
            authenticated = False

    except TimeoutException:
        authenticated = False
        await ctx.send("Authentication timeout. Please check your credentials or XPaths.")
        return False

    return True

We know our authentication was successful if we are redirected to the lobby. If we are not redirected, we will send a message to the user that the authentication failed. The way that this is implemented is by checking the current URL of the driver. If the current URL is the lobby, we know that we have successfully authenticated. The flag authenticated is set to true so when the bot is running by itself, it can check if it is authenticated to proceed.

Collecting Bonus and Countdown

To claim the bonus, we just use simple webdriver methods to find and click the buttons by CSS selectors and XPATHS. Using await ctx.send() is to send a message to discord. For the countdown timer, we use the .zfill method to format the time to be in the format HH:MM:SS.

# Function to claim DingDingDing daily bonus
async def claim_dingdingding_bonus(driver, ctx):
    try:
        # Click the bonus button in the lobby
        bonus_button = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.CSS_SELECTOR, "#__nuxt > div > div:nth-child(1) > aside.sidenav > div.sidenav__cont > div > div.sidenav__actions > button.btn.btn--nav.btn--rewards > span.btn__label"))
        )
        bonus_button.click()
        print("DingDingDing Daily Bonus Button Found!")

        # Click the collect button
        collect_button = WebDriverWait(driver, 10).until(
            EC.element_to_be_clickable((By.XPATH, "/html/body/div[1]/div/div[1]/div[6]/div/div[2]/div/div/button[2]"))
        )
        collect_button.click()

        await ctx.send("DingDingDing Daily Bonus Claimed!")

    except TimeoutException:
        print("COLLECT button not found! Check XPATH of claim button!")
        return False

    return True

# Function to check the countdown for the next bonus
async def check_dingdingding_countdown(driver, ctx):
    try:
        # Retrieve countdown elements
        hours_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[1]/div[6]/div/div[2]/div/div/div/span/div[1]/span"))
        )
        minutes_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[1]/div[6]/div/div[2]/div/div/div/span/div[2]"))
        )
        seconds_element = WebDriverWait(driver, 10).until(
            EC.presence_of_element_located((By.XPATH, "/html/body/div[1]/div/div[1]/div[6]/div/div[2]/div/div/div/span/div[3]"))
        )

        # Format countdown time
        countdown_message = f"Next DingDingDing Bonus Available in: {hours_element.text.zfill(2)}:{minutes_element.text.zfill(2)}:{seconds_element.text.zfill(2)}"
        await ctx.send(countdown_message)

    except TimeoutException:
        await ctx.send("Failed to retrieve DingDingDing countdown timer.")
        return False

    return True

discord bot

Conclusion

That’s it! We have successfully authenticated into DingDingDing, claimed the daily bonus, and checked the countdown for the next bonus. The image above is what it looks like when invoked with a command for the discord bot. This is only a small example of what you can do with Selenium WebDriver combined with docker and chromium.