How I hacked a course platform built with WordPress

So in my daily web surfing I see a lot of websites and sometimes just for fun I investigate to understand how to break them.

This is one of the cases, it wasn’t covered by the company bug bounty because it isn’t (really) a security issue but let me do (and pass) all the courses on this platform with a JavaScript script.

The application

In this portal there is a Courses feature with Quizzes to check your knowledge, otherwise you don’t get points and a certificate to unlock more stuff that you can do.
This UI/UX is built with jQuery using the WordPress AJAX API, doing various requests to check your answer at every question you reply.
At the end there is the final request to check the whole status informing the user about which was the right answer.

This quiz platform was custom made using the WordPress framework itself.

Before showing a video heavily censored of the first hack with cURL let me explain some things about the exploit (it wasn’t considered so, but for me it is):

  • In WordPress
    • There is the AJAX API that is more and more abandoned for various issues
      • There are 2 ways to declare an AJAX action, for logged and non-logged users
        • Often developers confuse them and you can imagine the problems
  • This portal provided to the user at the end of the Quiz a recap about the wrong answers
    • But those information aren’t sent to the browser at the quiz ending but after every step
    • So my first thought was that I can get the list of all the correct answers and later check just the right one, when I retry it
      • Yes, the system propose the same questions in a different order
    • After, I was able to craft the cURL command sharing the session cookie of my user, with the action, the lesson id with the question id
      • I discovered that without sending a question I get the right answer ID
      • And that doesn’t matter, I can do various requests for the same question and the one I picked overwrite the previous (in their DB)
        • In this way I can do a series of cURL commands with just picking the answer ID shared from the previous call and do the whole course
    • The system at the end lets the user retry the course if the user fails, but doesn’t check if I already started the course or the fact that I already answered the question
      • In this way I can retry multiple times for the same question without doing the whole quiz
    • The question ID was available in the HTML itself, the lesson id it was in the quiz/page URL

This is the JavaScript code (in jQuery) in the page that helped understand how works:

Video with cURL commands

Let me show you the video about it, the ID I am using for the request is marked and there is the console with the request.

So I finished my first course with pure cURL commands but it was very boring. Why I shouldn’t automatize with JS in the page?

Code

jQuery('.question-container').each(function(){
  var question_id = jQuery(this).data('question-id');
  var question = {}
  question[question_id] = '12'
  var data = {
    'action':'_course_check_question_answer',
    'lesson_id':"79",
    'question':question
  };
  jQuery.ajax({
    type: 'POST',
    dataType: 'json',
    url: ajax_course_object.ajaxurl,
    data: data
  }).then(function (res) {
    if (!res.data.correct) {
    data.question = {}
    data.question[question_id] = res.data.answers[0]
    }
    jQuery.ajax({
        type: 'POST',
        dataType: 'json',
        url: ajax_course_object.ajaxurl,
        data: data
    })
  })
})

By running this code in the browser console I was able to do a loop of every question in the page, try with a request, get the right question and do it again.

After that with this code:

jQuery.ajax({
    type: 'POST',
    dataType: 'json',
    url: ajax_course_object.ajaxurl,
    data:
    {
        "action": "complete_quiz",
        "course_id": "8",
        "lesson_id": 79
    }
});

There will be the final requests that check if I completed the Quiz, and will pass successfully.

I can’t show you other screenshots as the company asked me to censor everything.

So what was built wrong in this app?

  • It isn’t a jQuery failure (the UX was built with that) because also with other solution, like React, it is possible to investigate the code at the end. With jQuery is just more simple to read it.
  • The first issue is that everything is shared to the browser in real time, usually quiz systems don’t show the wrong answers to the user and everything is random
  • The next one is that is possible to save the quiz result everytime in their DB
    • Makes more sense to let the user to do the quiz and at the end all the results are sent to the server with the report
  • WordPress isn’t the culprit, it is just the development around and the UX that is faulty

Thanks to Gabriele Ponzo for the review.

Liked it? Take a second to support Mte90 on Patreon!
Become a patron at Patreon!

Leave a Reply

Your email address will not be published. Required fields are marked *