Registered Information Security Specialist Exam, Spring 2024 (Reiwa 6) PM Q1 Commentary — JWT alg=none, API Authorization, and Interim WAF Mitigation
· Go Komura · Registered Information Security Specialist, Registered Security Specialist, API, API Security, JWT, Authentication, Authorization, WAF, Log4Shell, Information Security, Vulnerability, IPA
“We verify the JWT’s signature, so the user ID can be trusted.”
That statement is only half right.
Question 1 of the PM (afternoon) session of the Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination is built around an API called from a smartphone app.1 On successful authentication a JWT is issued, and that JWT is attached to calls against the API that fetches and updates user information. At first glance, this is an entirely ordinary setup.
However, the assessment turns up the following four issues.
- Changing the JWT header’s
algtononelets an unsigned JWT pass. - Keeping a valid JWT but changing
midto a different user ID lets you read or update someone else’s information. - Adding an undocumented
status=paidturns a free-tier user into a paid user. - The four-digit authentication code delivered by email can be brute-forced with no limit on attempts.
All four look like “authentication-adjacent vulnerabilities”, but the causes are not the same. What is being broken are separate boundaries: token integrity, object-level authorization, property-level authorization, and attempt-rate limiting.
The second half of the question adds yet another topic. A critical vulnerability is disclosed in a widely used open-source library, one that lets an attacker execute code remotely by abusing JNDI Lookup. Neither a fix nor a finished WAF rule exists yet. In the meantime, the question asks how to confirm the impact, where the WAF should look, and why the initial WAF mode should be “detect” rather than “block”.
This article uses the official model answers2 and the grading commentary3 as its foundation, and works through not just the answer to each question but why that is the answer, and how much more rigorously you should design for it in practice.
flowchart TB
accTitle: Overview of the question
accDescr: Shows the trust boundary broken at each stage - authentication code, JWT, API authorization, and library vulnerability
user[User app]
auth[4-digit auth code]
jwt[JWT issuance]
api[User API]
log[Log output]
vuln[Vulnerable library]
ext[Remote code execution]
user --> auth
auth -->|No attempt limit| jwt
jwt -->|Allows alg=none| api
api -->|Trusts mid| db1[Read/update another user's data]
api -->|status=paid| db2[Change billing status]
api --> log
log --> vuln
vuln -->|JNDI/LDAP/HTTP| ext
Figure 1: Overview of the question. A different trust boundary is broken at each stage.
1. The Bottom Line First
- The property of a RESTful API not holding session state is called statelessness. This does not mean the server holds no database or user state whatsoever
- A four-digit authentication code has 10,000 possible values. At 10 attempts per second, an attacker succeeds after an average of 5,000 attempts, i.e. 500 seconds — shorter than the 10-minute validity period, so expiry alone cannot stop it
- The minimum countermeasure against
alg=noneis confirming that the JWT header’salgis notNONE. In practice, though, you should fix the set of allowed algorithms on the server side - Even with a valid JWT, the request’s
midmust not be trusted. Either match the user ID inside the JWT againstmid, or, more safely, don’t acceptmidfrom the client at all and determine the target from the JWT - Adding
status=paidis a Mass Assignment problem, where properties outside the specification get bound straight onto the internal object. Use the update DTO as an allowlist, and never let the user change billing status - The model answer for the brute-force countermeasure is logic that locks the account once the number of consecutive failures exceeds a threshold. In practice, layer on graduated delays and per-source controls as well
- To confirm the impact of a newly disclosed critical vulnerability, rather than issuing a destructive command, record accesses to a test server’s
index.htmlto confirm that remote code execution actually reaches it - Because the attack string is carried in an HTTP header, the WAF’s inspection target is
Header. As a regular expression that handles case swapping, use something like\W[jJ][nN][dD][iI]\W - The benefit of starting the WAF in “detect” mode is that it prevents legitimate business traffic from being blocked by a false positive. When an alert arrives, examine whether it is a genuine attack, then move to blocking mode once the rule has been tuned
- The WAF is only an interim measure; the root fix is updating the affected library to a patched version
2. How the Scenario Maps to the Questions
The scenario is set at Company G, which is launching a new healthcare service. Users enter data such as meals and body weight through a smartphone app and receive health-risk assessments and dietary advice. The system is built on the cloud, combining an API gateway, event-driven processing, and a managed database.
The question paper abstracts away specific product and service names. This article likewise does not reproduce IPA’s diagrams or text, but paraphrases only the structure needed to understand the questions.
| Question | Topic | Chapter of this article |
|---|---|---|
| Question 1 | The nature of RESTful APIs | Chapter 4 |
| Question 2(1) | Time to brute-force the 4-digit code | Chapter 5 |
| Question 2(2) | JWT alg=none |
Chapter 6 |
| Question 2(3) | Accessing another user via mid |
Chapter 7 |
| Question 2(4) | The flaw that accepts an out-of-spec status |
Chapter 8 |
| Question 2(5) | Brute-force countermeasures | Chapter 9 |
| Question 3(1) | Safely confirming that a vulnerability exists | Chapter 11 |
| Question 3(2)(3) | Where the WAF looks, and the regular expression | Chapter 12 |
| Question 3(4) | The benefit of detect mode and how to operate it | Chapter 13 |
The grading commentary notes that the overall correct-answer rate was about average. It also points out, however, that the correct-answer rate was somewhat lower for the JWT-tampering countermeasure in Question 2(2) and for the mechanism needed on the verification server in Question 3(1). Neither can be answered from vocabulary alone. You have to trace which value the attacker changed, which process it flowed into, and where it ended up being trusted.
3. This Is Not Just One “Authentication Problem”
Laying the whole question out by trust boundary gives the following.
[User ID / password]
|
v
[4-digit code check] ---- no attempt limit ----> brute force
|
v
[Issue JWT]
|
v
[JWT library] ------- allows alg=none ------> user ID tampering
|
v
[User API]
| |
| +-- passes status through wholesale ----> property-level authorization flaw
|
+-- trusts mid -----------------------> object-level authorization flaw
[Log external input]
|
v
[Vulnerable library] ---- JNDI/LDAP/HTTP ------> remote code execution
The most important distinction here is the following.
| Check | Question it asks | Broken example in this scenario |
|---|---|---|
| Authentication | Who are you | Brute-forcing the 4-digit code |
| Token verification | Has that identity information been tampered with | alg=none |
| Object-level authorization | May this user access this user’s data | Swapping mid |
| Property-level authorization | May this field be changed | status=paid |
| Input-to-execution boundary | Is external input being interpreted as a command | JNDI Lookup |
Passing one check is never a reason to skip the next one. A user with a valid JWT is not necessarily allowed to read someone else’s data. A user allowed to update their own data is not necessarily allowed to change their billing status too.
Once you can separate these stages, the answer to each question stops being something you memorise.
flowchart LR
accTitle: The difference between authentication and authorization
accDescr: Authentication confirms the subject, authorization confirms what that subject is permitted to do
auth[Authentication<br/>who you are]
authz[Authorization<br/>what you may do]
auth --> authz
Figure 2: The difference between authentication and authorization. Authentication comes first; authorization is a separate check.
4. Question 1 — What Does “Stateless” Mean
Question 1 asks about one of the design principles of RESTful APIs: the property of not performing session management.
The answer is stateless.
Stateless means the server does not need to remember the conversational state of the previous request, because each request on its own carries everything needed to process it. In this question, the smartphone app attaches a JWT to the Authorization header on every request. The server verifies that JWT and identifies the user for that request from it.
A common misreading is to take “stateless” as “the server holds no state at all”. In reality, it ordinarily holds the following state.
- The database storing user information and health data
- Billing status
- The authentication code’s value, expiry, and failure count
- The JWT signing key
- Revocation information, for designs that use a revocation list
- Logs and audit records
What it does not hold is server-side session state that exists purely to continue a conversation, as a precondition each API call depends on.
Being stateless also does not automatically improve security. Sending the JWT on every request does make horizontal scaling easier, but if the JWT verification is wrong, that error spreads uniformly across every node too. An architectural property and security correctness are two different things.
5. Question 2(1) — A Four-Digit Code Is Cracked in an Average of 500 Seconds
The authentication API sends a four-digit number by email once the user ID and password match. It then issues a JWT once the user ID and the four-digit code match. The code is valid for 10 minutes from generation.
In the assessment, 10 attempts per second were possible. The question asks how many seconds it takes, on average, to break through.
The Calculation Is “Half the Candidate Space”
A four-digit number, including a leading zero, has the following 10,000 possibilities.
0000, 0001, 0002, ... , 9999
If the correct answer is drawn uniformly at random, an attacker trying candidates in order without repeats reaches the correct one, on average, after half the candidate space.
Average number of attempts = 10,000 / 2 = 5,000
Average time = 5,000 / 10 attempts per second = 500 seconds
So blank b is 500.
The worst case takes up to 1,000 seconds, but the question asks for the average. And the code’s validity period is 600 seconds — longer than the average break time of 500 seconds. That is why it is judged “likely to be broken”.
flowchart LR
accTitle: Sense of scale for the 4-digit auth code
accDescr: Trying 10,000 candidates at 10 per second averages 5,000 attempts and 500 seconds, which is less than the 600-second validity period
A[10,000 candidates] -->|Average 10,000 / 2 = 5,000 attempts| B[Average break time 500 seconds]
C[Validity period 600 seconds] -->|500 seconds is less than 600 seconds| D[Can be broken within the validity period]
Figure 9: Sense of scale for the four-digit authentication code. Trying, on average, half the candidate space breaks it within the validity period.
Shortening Only the Expiry Time Loses If the Candidate Space Is Small
The strength of an authentication code is determined by neither the digit count alone nor the validity period alone.
Number of attempts possible during the validity period
= attempts per second x validity period
= 10 x 600
= 6,000 attempts
Trying non-repeating values in order, an attacker can check 60% of the 10,000 possibilities within the validity period. Setting an expiry time is not sufficient on its own if the number of attempts is not limited.
The current NIST SP 800-63B requires at least six digits for short-term secrets used in out-of-band authentication, and mandates an attempt-rate limit whenever the secret has fewer than 64 bits of entropy. It also calls for email not to be used for out-of-band authentication.4 The exam answer works within the given specification of a four-digit code sent by email, but for a new design in practice, that premise itself should be reconsidered.
6. Question 2(2) — alg=none Is a Problem of “Letting the Attacker Choose the Verification Method”
The JWT in this question consists of three parts: header, payload, and signature.
base64url(header).base64url(payload).base64url(signature)
The header recorded RS256 as the algorithm used for signing. The payload contains the user ID, issue time, and expiry.
The assessor changed the following two things.
- Change the header’s
algfromRS256toNONE. - Change the payload’s user ID to a different user.
Sending that JWT, verification succeeded and let the assessor impersonate someone else.
flowchart LR
accTitle: Flow of the JWT alg=none attack
accDescr: Changing alg to none in a valid JWT and rewriting the user ID lets the request through
A[Valid JWT<br/>alg=RS256<br/>user=user01] -->|Change header alg to none| B[Tampered JWT<br/>alg=none<br/>user=user02]
B -->|Skips signature verification| C[Server accepts it<br/>as user02]
Figure 3: Flow of the JWT alg=none attack. The attacker chooses the verification algorithm.
none Is Not a Typo
RFC 7519 defines an “Unsecured JWT” — a JWT with neither a signature nor encryption, whose alg is none.5 So the value none is not something that simply doesn’t exist in the spec.
The problem is that an API that should accept only signed JWTs accepted the none the attacker specified.
Written conceptually, the vulnerable process looks like this.
1. Read the JWT header.
2. Look at the alg written in the header, and choose the verification method.
3. If alg is none, do not verify the signature.
4. Trust the user ID in the payload.
The very strength of the security is being chosen from input the attacker controls.
The Exam Answer
The question asks, in 20 characters or fewer each, what data the fixed library Q should verify, and what that verification should check.
The model answer is as follows.
| Item | Gist of the answer |
|---|---|
| Data to verify | The value specified in the JWT header’s alg |
| What to verify | That it is not NONE |
As a direct fix for the vulnerability described in the question, this is correct.
In Practice, Don’t Settle for “Anything but NONE”
Here we need to separate the exam answer from the practical recommendation.
RFC 8725 states that a JWT library should let the caller specify a set of permitted algorithms, and that nothing outside that set may be used.6 In other words, the idea is this.
Bad approach:
Accept if token.header.alg != "none"
Good approach:
Accept only if it is contained in serverConfig.allowedAlgorithms
e.g. allowedAlgorithms = ["RS256"]
Rejecting only none can still leave other weak algorithms, or the possibility of algorithm confusion where a public-key scheme is mistaken for a symmetric-key one. The principle is not to keep adding negative conditions for what to accept, but to fix a narrow, positive set of what is allowed.
JWT verification should confirm not just the algorithm but, depending on the use case, at least the following as well.
| Item | What to confirm |
|---|---|
| Signature | Can it be verified with the expected key and algorithm |
iss |
Is it a trusted issuer |
aud |
Was the token issued for this API |
exp |
Is it within its validity period |
nbf |
Is it not before its “not valid before” time |
sub or user ID |
Is it a valid subject within the application |
| Token type | Is an ID token being confused with an access token, etc. |
In this question the payload’s key name is user, but in practice you should either use the standard sub or clearly define the meaning of a custom claim.
flowchart TB
accTitle: Safe versus unsafe JWT verification
accDescr: Unsafe verification depends on alg, safe verification uses a server-side allowlist
subgraph "Unsafe verification"
D1[Read alg from the JWT header]
D2[Accept if alg is none]
D1 --> D2
end
subgraph "Safe verification"
S1[Server-configured allowed algorithms<br/>e.g. RS256]
S2[Confirm the JWT header's alg<br/>is in the allowlist]
S3[Verify signature, iss, aud, exp]
S1 --> S2 --> S3
end
Figure 4: Safe versus unsafe verification. In practice, fix a narrow set of allowed algorithms.
Base64url Is Not Encryption
There is another common misunderstanding about JWTs. The header and payload are represented in base64url, but that is not encryption. Anyone can decode and read them.
What the signature guarantees, and only when it verifies correctly, is that the content has not been tampered with since issuance. It does not mean personal information you want kept secret may be put in the payload of a signed JWT.
7. Question 2(3) — Even With a Valid JWT, Changing mid Read Someone Else’s Data
Next is an attack that does not tamper with the JWT itself.
The user API receives a user ID called mid on GET or PUT. Common module P fetches or updates, in the database, the user information tied to that mid.
The attack’s structure is simple.
User ID inside the JWT: user01 <- a correctly signed JWT
Request's mid: user02 <- changed by the attacker
The JWT’s signature is valid, so authentication succeeds. But the API trusts mid=user02 as given, and returns user02’s information.
This is a textbook case of what the OWASP API Security Top 10 2023 calls Broken Object Level Authorization (BOLA). Whenever data is accessed using an object ID that the user has specified, authorization for that specific object must be checked every time.7
flowchart LR
accTitle: The BOLA attack
accDescr: Using a valid JWT while changing the request's mid to a different user ID
A[Attacker] -->|JWT user01<br/>mid user02| B[User API]
B -->|Trusts mid| C[Returns user02's data from the DB]
Figure 5: The BOLA attack. Authentication passes, but authorization was never checked.
The Question’s Answer
Underline 2 in Table 5 asks, in 40 characters or fewer, for the processing to add to the call into common module P.
The model answer is:
Logic that verifies whether the user ID contained in the JWT matches the value of
mid
The benefit of verifying inside common module P is that the same authorization check is easy to apply to both GET and PUT, and to any future API that uses P as well. Copying the same comparison into each screen or endpoint individually means it will be missing somewhere.
A Safer Design Is Not to Accept mid at All
For an API that only fetches or updates the caller’s own information, there is no need to accept a user ID from the client at all.
GET /users/me
Authorization: Bearer <JWT>
On the server side, the subject is extracted from the verified JWT.
principal = validateJwt(request.authorization)
userId = principal.subject
return repository.getUser(userId)
The same applies to updates.
principal = validateJwt(request.authorization)
input = validateProfileUpdate(request.body)
repository.updateProfile(
userId = principal.subject,
name = input.name,
age = input.age
)
A comparison check protects you if you write it. But a design that never accepts the target ID from outside reduces the very existence of the bug class where you forget to write that comparison.
If an administrator needs to operate on another user’s information, split it as follows.
PUT /users/me For general users
PUT /admin/users/{userId} For administrators
For the administrator route, require separate permissions, audit logging, and, if needed, re-authentication. This makes the authorization policy’s boundaries much more visible than “add an exception to the general-user API for administrators only”.
flowchart LR
accTitle: How to prevent BOLA
accDescr: Instead of using the request's mid, decide or check the target from the JWT's subject
A[User] -->|GET /users/me + JWT| B[API]
B -->|Take sub from JWT| C{If mid is present<br/>does it match sub}
C -->|Match| D[Return own data]
C -->|No match| E[403 reject]
B -->|No mid| F[DB lookup by JWT sub]
Figure 6: How to prevent BOLA. Either don’t accept mid, or check it against the JWT’s subject.
Distinguishing Authentication From Authorization in One Sentence
Both on the exam and in practice, the following phrasing helps.
- Authentication: who you are
- Authorization: what that person may do
A successful JWT signature verification only gets you as far as “the subject this token represents can be trusted”. Whether “that subject may read user02” must be confirmed separately.
8. Question 2(4) — status=paid Is a Property-Level Authorization Flaw
The user API’s specification defines the following as update parameters.
mid User ID
name Name
age Age
However, the assessor added the following value, which is not in the specification.
status=paid
The status of a free-tier user then changed to that of a paying user.
According to the question, service L did not validate the parameters it received; it passed all of them straight to common module P, which was built so it could update the database directly.
The answer for blank c is common module P.
flowchart TB
accTitle: Mass Assignment
accDescr: An out-of-spec status=paid is added and applied wholesale onto the internal object
A[API spec<br/>mid / name / age] -->|Attacker adds status=paid| B[Request body]
B -->|Auto-bound| C[Common module P]
C -->|Saved to DB| D[Billing status changed to paid]
Figure 7: Mass Assignment. An out-of-spec property is applied wholesale onto the internal object.
The Difference From BOLA
The mid swap from the previous chapter and this status addition look similar, but the granularity being protected differs.
| Vulnerability | What the attacker changes | What should actually be checked |
|---|---|---|
mid swap |
The target object | May this user access this user record |
status addition |
A property within the object | May this user change this field |
The OWASP API Security Top 10 2023 treats the latter as Broken Object Property Level Authorization, folding what was previously called Mass Assignment into this category.8
“Putting JSON Straight Into the Entity” Is Dangerous
The vulnerable implementation, conceptually, looks like this.
entity = repository.find(body.mid)
bindAllProperties(entity, body)
repository.save(entity)
Even if the screen only has input fields for name and age, an attacker can craft the HTTP request directly. A field’s absence from the UI is not a security boundary.
A safe implementation makes the updatable fields explicit.
input = parseExactSchema(body, fields = ["name", "age"])
entity = repository.find(authenticatedUserId)
entity.name = input.name
entity.age = input.age
repository.save(entity)
Two things matter here.
- The update input type should hold only the fields a user is allowed to change.
- Rather than silently ignoring unknown, out-of-spec fields, reject them as an error if possible.
Silently ignoring unknown fields hides the fact that an attack failed, but it also lets you miss client implementation mistakes and signs of an attack. Unless there is a compatibility reason not to, rejecting with a strict schema makes investigation easier.
flowchart TB
accTitle: Property-level authorization
accDescr: The update DTO holds only an allowlist, and unknown properties are rejected
subgraph "Update DTO - allowlist"
D1["name"]
D2["age"]
end
A[Request body] -->|Schema validation| B{Only allowed<br/>fields present}
B -->|Yes| C[Update entity name/age]
B -->|No| D[Return an error]
E[Payment service<br/>verified notification] -->|Dedicated path| F[Update status=paid]
Figure 8: Property-level authorization. Restrict updatable fields with an allowlist, and change billing status only through a separate path.
status Should Change Only From the Payment Result
status=paid is not part of the user profile. It is state derived from a server-side fact: that payment succeeded.
User profile update
-> only name / age can be changed
Verified notification from the payment service
-> match paymentId
-> prevent duplicate processing
-> change status to paid
Even when stored in the same database column, the authority to change it and the path used to change it are different things. Using an internal entity directly as an external API’s input type erases that boundary.
9. Question 2(5) — Brute-Force Countermeasures Hold the Failure Count as State
For the brute-forcing of the four-digit code, blank d in Table 5 asks, in 30 characters or fewer, for the processing that belongs there. The threshold is 10.
The model answer is:
Logic that locks the account once the number of consecutive failures exceeds the threshold
This does not contradict the statelessness from Question 1. Not holding an API call’s conversational state as a server session, and persisting the failure count needed for a security decision, are two different things.
flowchart LR
accTitle: With and without an attempt-rate limit
accDescr: Without a limit, the code is broken in an average of 500 seconds, but a failure-count limit sharply slows the attack
subgraph "Without a limit"
A1[10 attempts per second] -->|About 500 seconds| B1[Authentication succeeds]
end
subgraph "With a limit"
A2[Lock after 10 failures] -->|Attack speed collapses| B2[Account locked]
C2[Graduated delay] --> B2
end
Figure 10: With and without an attempt-rate limit. A failure-count limit can practically stop brute-forcing.
In Practice, Don’t Rely Solely on Permanent Locking
A per-account attempt limit is necessary, but if an attacker knows someone else’s user ID, they can deliberately fail 10 times to lock the legitimate user out. In practice, therefore, combine the following.
| Control | Role |
|---|---|
| Per-account failure count | Stops brute-forcing against a single account |
| Graduated wait times | Tolerates a legitimate user’s input mistakes while slowing the attack down |
| Controls by source IP, device, ASN, etc. | Suppresses attacks that try a small number of times against many accounts |
| Risk-based decisions | Applies stronger restrictions for unusual regions, devices, or speeds |
| Notifying the user | Lets the user notice an attack or their own mistake |
| A safe recovery procedure | Keeps the unlock channel from becoming an attack route itself |
Furthermore, when a code is resent, the failure count must not be reset to zero — otherwise an attacker can replenish their attempt budget every time they call the resend API. The current NIST SP 800-63B likewise requires that the failure count not be reset even when a new authentication secret is generated.4
Make the Authentication Code Single-Use
The question focuses on the expiry time, but in practice the following are also needed.
- Invalidate a successful code immediately.
- Reject reuse of the same code.
- Do not leave the code itself in logs.
- Make the response such that success or failure of the code check cannot be used to infer whether a user exists.
- Put an attempt limit on the code-sending API as well.
As long as a short secret is being used, security cannot be left to random generation alone.
flowchart TB
accTitle: Authentication code countermeasures
accDescr: Beyond digit count and expiry, protect with attempt limits, reuse rejection, notification, and more
A[Authentication code] --> B[Increase digit count]
A --> C[Shorten the expiry]
A --> D[Attempt-rate limit]
A --> E[Invalidate after success]
A --> F[Don't reset failure count on resend]
A --> G[Don't leave the code in logs]
A --> H[Per-source control]
Figure 11: Authentication code countermeasures. Combine digit count and expiry with attempt control and operational practices.
10. Distinguishing the Four Parts of Question 2 on One Page
The points in Question 2 that are easy to conflate, organised by the value the attacker controlled.
| Attack | Value the attacker changed | What should not have been trusted | Root fix |
|---|---|---|---|
| JWT tampering | JWT header’s alg, payload’s user ID |
The verification algorithm declared by the token itself | Fix the allowed algorithms on the server side |
| Reading another user’s information | Request’s mid |
The target ID specified by the client | Match against the JWT’s subject, or determine the target ID from the JWT |
| Upgrading to a paid user | Out-of-spec status |
All auto-bound properties | Make updatable properties an allowlist |
| Breaking the 4-digit code | Candidates for otp |
Unlimited authentication attempts | Add attempt-rate limits, delays, and risk decisions |
It matters not to lump all of this together as “validate the input”.
algis a cryptographic policy.midis object-level authorization.statusis property-level authorization.otpis resistance to online guessing.
Even within the same HTTP request, the reason each one must be protected is different.
11. Question 3(1) — Confirming Remote Code Execution Without Causing Damage
After the service launches, a critical vulnerability V is disclosed in library H, a widely used open-source library. The question’s sequence of events is as follows.
- The attacker puts a string containing a JNDI Lookup into an HTTP header and sends it.
- The target server logs that value.
- The vulnerable library evaluates the JNDI Lookup and queries the attacker’s LDAP server.
- The LDAP response returns the URL of the attacker’s HTTP server.
- The target server fetches the class file and executes the command.
With the specific product name withheld, this reads as an attack of the Log4Shell (CVE-2021-44228) type. Apache’s own description likewise describes the vulnerability as one where, if an attacker controls log messages or parameters, they can execute arbitrary code loaded from an LDAP server.9
flowchart LR
accTitle: Confirmation flow for a Log4Shell-type vulnerability
accDescr: Use a harmless callback to confirm whether the chain from JNDI to remote code execution actually goes through
A[Attacker] -->|Inject a jndi/ldap payload<br/>into x-api-version| B[Vulnerable server]
B --> C[Log processing]
C -->|JNDI Lookup| D[Malicious LDAP server]
D -->|HTTP URL response| E[Malicious HTTP server<br/>index.html]
E -->|Record the GET| F[Test server<br/>access log]
F -->|Confirm reachability| G[Vulnerability confirmed]
Figure 12: Confirmation flow for a Log4Shell-type vulnerability. Reachability is confirmed by recording an HTTP access rather than issuing a destructive command.
The Verification Code Triggers Only a Harmless HTTP Access
Company G runs verification code that has no impact on the system, to confirm whether vulnerability V can be exploited from outside. The only command the verification code issues is fetching the test server’s index.html.
Question 3(1) asks what needs to be implemented on the test server in order to confirm that the command was executed.
The model answer is:
A mechanism that records and lets you confirm accesses to the test server’s index.html
If the web server’s access log records a GET from the target server, that confirms, at minimum, that the following chain went through.
External HTTP request
-> log processing
-> JNDI Lookup
-> LDAP response
-> class retrieval
-> verification command execution
-> HTTP access to the test server
Why “Displaying Text on Screen” Is Not Enough
The target of the attack is the server. There is no guarantee that anything changes on the user’s browser screen. Also, even where the vulnerability exists, the outbound communication partway through can be blocked by a firewall.
Recording the access on the test server side produces observable evidence that the target server actually reached the outside world.
When performing this kind of verification in practice, always observe the following.
- Obtain explicit authorisation from the owner of the target system.
- Use a verification method that has no impact on production, or an acceptably small one.
- Do not use destructive commands such as writes, deletes, or configuration changes.
- Manage the verification domain and server yourself.
- Record the verification time, source, target, and expected callback.
- Tear down any temporary LDAP or HTTP servers and credentials after verification.
“Confirming that arbitrary code execution is possible” and “executing arbitrary dangerous code” are not the same thing. Keep the side effects to the minimum needed to meet the goal.
12. Question 3(2)(3) — The WAF Inspects the HTTP Header
Service N’s WAF lets you choose GET, POST, PUT, ANY, Header, COOKIE, or Multipart as the inspection target.
The attack code goes into the value of an HTTP header called x-api-version. So blanks e and f in Table 6 are both Header.
Map the Location Given in the Text Directly to the WAF’s Inspection Target
This is less about general knowledge and more about reading the data flow in the question text.
Where the attack string is placed:
x-api-version header
|
v
The WAF's inspection target:
Header
It is neither a GET parameter nor a POST body. Rather than looking at the WAF’s list of features and picking ANY because “it looks like an attack”, answer with the location where the question text says the attacker put the value.
Handling Case Swapping
The first proposal was, conceptually, the following rule.
Header \Wjndi\W block
Header \Wldap\W block
But swapping case, as in jNdI, evades a pattern that only matches lowercase.
The model answer for Question 3(3) is either of the following.
\W[jJ][nN][dD][iI]\W
\W(j|J)(n|N)(d|D)(i|I)\W
In the question booklet the backslash can look like a yen sign in the Japanese-locale glyph, but as a regular expression it is \W. \W matches any character other than alphanumerics and underscore. In JNDI Lookup syntax, non-word characters such as ${ and : appear immediately before and after jndi, and the pattern is written to catch those too.
The same idea can be applied to make the ldap side case-insensitive too.
\W[lL][dD][aA][pP]\W
Don’t Treat This Regex as a “Complete Log4Shell Countermeasure”
The exam is asking for a regular expression that handles the evasion technique shown in the question text. Real-world attacks can involve string splitting, alternative Lookups, encoding, other protocols, and other variations that are hard to cover with signatures alone.
The practical positioning, therefore, is as follows.
- Interimly block currently known attack patterns with the WAF.
- Investigate whether the affected library is actually present.
- Restrict outbound LDAP, RMI, and unnecessary HTTP traffic.
- Update to a patched version.
- After updating, still check the logs and investigate whether a breach occurred.
The WAF is a layer that buys time until a patched version is available.
flowchart TB
accTitle: Where the WAF fits
accDescr: The WAF is an interim mitigation layer, the root fix is updating the library to a patched version
A[Critical vulnerability disclosed] --> B[Confirm impact]
B --> C[Interim mitigation]
C -->|WAF rule<br/>detect/block| D[Temporarily stop the attack pattern]
C -->|Restrict outbound traffic| E[Close off the abuse route]
D --> F[Update to the patched library]
E --> F
F --> G[After-the-fact review and prevention]
Figure 13: Where the WAF fits. The WAF only buys time until a patch arrives; the root fix is the update.
13. Question 3(4) — Why Start With “Detect”
For the updated WAF rule, Z, a Registered Security Specialist, advises setting the mode to “detect” rather than “block” for a fixed period after it goes live in production.
The question asks, in 25 characters or fewer each, for the benefit of using detect mode and for what should be done to minimise damage.
The model answer is:
| Item | Gist of the answer |
|---|---|
| Benefit | It can prevent blocking caused by a false positive |
| What to do | Examine whether it is an attack whenever an alert is received |
Detect Mode Is Not a “Do Nothing” Mode
In detect mode, traffic that matches the rule is still let through, but it is logged and an alert is raised. Even if the string jndi or ldap happens to appear inside a legitimate API call, business is not stopped immediately.
In exchange, the operations side needs to do the following.
Alert received
|
v
Check the request in question
|
+-- Legitimate traffic -> narrow the rule, consider an exception
|
+-- Attack -> isolate the target, preserve logs, investigate impact, move to blocking
If no one looks at the alerts, detect mode has no protective effect at all. Detection only works paired with an operational process that observes and judges.
The Path From Detect to Block
A typical rollout procedure is as follows.
- Run detect mode against real traffic.
- Classify hits as false positives or true positives.
- Tune the target header, path, API, word boundaries, and so on.
- Confirm that the impact on legitimate traffic is acceptable.
- Switch to blocking mode.
- Monitor the number of blocks and the business impact.
This, though, is the principle for ordinary times. When a vulnerability is critical, is actively being exploited, and no alternative exists, the outage caused by a breach can be judged to outweigh that caused by a false positive, and blocking from the start is chosen instead. In the exam’s scenario, detect mode is chosen first in order to confirm that the service can keep operating as before.
flowchart LR
accTitle: From WAF detect mode to block mode
accDescr: Observe alerts in detect mode, tune out false positives, then move to block mode
A[Detect mode] -->|Real traffic| B[Alert raised]
B --> C{Attack or<br/>false positive}
C -->|False positive| D[Tune the rule]
D --> A
C -->|Attack| E[Switch to block mode]
E --> F[Monitor block count and business impact]
Figure 14: From detect to block. Observe and tune first, confirm the impact is acceptable, then move to blocking.
14. The WAF Is an Interim Measure; Updating Is the Root Fix
In the question, library H’s official site had neither a fix nor an interim workaround yet, and even the cloud provider’s comprehensive WAF rule was going to take up to 72 hours. So Company G confirms the impact itself and provisionally blocks at least the patterns it has already identified.
This sequence is the basic shape of incident response.
| Stage | Purpose | Response in this question |
|---|---|---|
| Confirm impact | Judge whether your own organisation is genuinely at risk | Confirm exploitability from outside with a harmless callback |
| Interim mitigation | Buy time until a fix arrives | WAF rules, detect/block, outbound traffic restrictions |
| Root fix | Remove the vulnerable cause | Update to a patched library |
| After-the-fact review | Check whether it has already been exploited | Investigate WAF, application, DNS, proxy, and other logs |
| Prevent recurrence | Speed up the next decision | Dependency inventory, SBOM, update procedure, contact route |
“We Don’t Know If We’re Using It” Is the Biggest Source of Delay
In the question, even when Company G asks Company F whether it uses library H, the answer takes time because a detailed configuration analysis is needed.
In practice, if you only start searching for JAR files after a critical vulnerability is disclosed, your response is delayed. You should have at least the following in place during peacetime.
- An inventory of direct and transitive dependencies.
- The components and versions actually included in your artefacts.
- Which services, containers, and devices they are deployed to.
- A procedure for updating a dependent library and rebuilding/redistributing.
- A contact route for approving emergency changes.
- The allowed destinations for outbound traffic, and the impact of blocking them.
- Where logs are stored and how to search them.
An SBOM is not the goal in itself. It is an index for answering, in a short time, “which running systems does this vulnerability affect”.
Don’t Stop Investigating Once You’ve Updated
You may have already been attacked around the time the vulnerability was disclosed. Updating to a patched version stops future exploitation, but it does not erase credentials that were already compromised or a backdoor that was already planted.
For a Log4Shell-type vulnerability, investigate at least the following angles.
- HTTP requests containing suspicious strings indicating JNDI or LDAP.
- Communication from the application server out to external LDAP, RMI, or HTTP.
- Unusual child processes being launched.
- Creation of suspicious JARs, classes, scripts, or executables.
- Access to cloud credentials or environment variables.
- Authentication, permission changes, and outbound transfers around the time of the update.
It matters not to conclude “we weren’t attacked” from WAF logs alone. There are internal paths that never pass through the WAF, and logs that were not retained in the past.
15. A Way of Reading That Makes Points Easier to Earn on the Exam
This question is less a knowledge test than an exercise in reading the gap between specification and implementation.
15.1 Separate “Specification” From “Implementation” in the Tables
In the status issue, a value absent from the API specification goes through in the implementation.
Specification:
mid / name / age
Implementation:
send all received parameters to P
Once you can see this gap, it becomes clear that blank c is common module P.
15.2 Draw a Line Under the Value the Attacker Changed
The value changed in each attack is as follows.
- JWT header’s
alg - JWT payload’s user ID
- API parameter
mid - Out-of-spec
status - Authentication API’s
otp - HTTP header
x-api-version
Almost every question is asking “where should that value be verified”.
15.3 Bring the Answer Back to the Question Text’s Own Terms
In practice you can call these “BOLA”, “Mass Assignment”, and “rate limiting”. But what the question asks for is concrete processing matched to the structure of the question text.
A poor example:
Perform authorization appropriately.
A good example:
Verify whether the user ID contained in the JWT matches the value of mid.
A poor example:
Take brute-force countermeasures.
A good example:
Lock the account once the number of consecutive failures exceeds the threshold.
Knowing the abstract name alone does not produce an answer that can be marked within the character limit.
15.4 For the WAF, Trace “Where It Was Put”
The WAF’s inspection target is decided not by guessing from the attack type, but from where the attack string was placed.
Put into the x-api-version header
↓
Inspection target is Header
The grading commentary’s note that Question 3(1)’s correct-answer rate was somewhat low is also because many answers did not match the attack flow in Figure 6. Simply redrawing the attack sequence with arrows already reveals what should be observed.
16. A Checklist for Real-World API Reviews
A checklist for carrying this question back into actual design and code reviews.
JWT Verification
- The allowed signature algorithm is fixed in server configuration.
noneand unexpected algorithms are rejected.- Signature,
iss,aud,exp, andnbfare verified as appropriate to the use case. - ID tokens, access tokens, and refresh tokens are not confused with each other.
- There is a procedure for key rotation and revocation.
- Information that must stay confidential is not put into the JWT payload.
Object-Level Authorization
- Changing the ID inside a request cannot reach another user’s data.
- Authorization is enforced across listing, detail, update, delete, and download alike.
- Authorization is enforced in a common layer that reaches the data, not in the screen.
- For self-only APIs, you considered deriving the target ID from the token instead.
- Administrator operations use a separate policy from the general-user API.
Property-Level Authorization
- The external input type and the database entity are kept separate.
- Updatable fields are enumerated as an allowlist.
- Out-of-spec properties are rejected or audited.
- State such as permissions, billing, approval, and ownership cannot be changed from user input.
- Responses also exclude unnecessary confidential properties.
Authentication Attempts
- There is a per-account failure-count limit.
- There is graduated delay and per-source control.
- The failure count is not reset when the code is reissued.
- The authentication code can only be used once.
- Authentication codes and passwords are not left in logs.
- The unlock/recovery procedure is not itself a weaker authentication route.
Critical Vulnerabilities in Dependent Libraries
- Running services can be mapped to their dependency versions.
- There is a procedure for verifying impact by a harmless method.
- Interim measures such as WAF rules and outbound restrictions can be applied.
- There is an operational process for a responsible person to review detection alerts.
- There is an emergency release route for updating to a patched version.
- Logs are investigated for possible exploitation before the update.
17. The Two Faces of Shared Components, as Seen Through This Question
This question features two shared components: JWT management library Q and common module P.
Shared components have major benefits.
- Fixing one place propagates the fix to every API that uses it.
- Authorization and verification logic don’t have to be duplicated into each feature.
- Test targets can be consolidated.
- Log and audit formats can be unified.
On the other hand, mistakes also spread across the whole system.
- If library Q accepts
alg=none, every API that uses JWTs becomes vulnerable. - If common module P accepts an arbitrary
midorstatus, both GET and PUT become vulnerable. - If vulnerable library H is used in the foundation, every route that logs HTTP headers becomes part of the attack surface.
What should be shared, therefore, is not mere data access. It is necessary to share the security invariants themselves, and rigorously verify that shared component in isolation.
For example, make P’s contract as follows.
P.getOwnUser(authenticatedSubject)
P.updateOwnProfile(authenticatedSubject, ProfileUpdate{name, age})
It is safer not to expose a low-level API like the following directly to ordinary callers.
P.getUser(arbitraryMid)
P.updateUser(arbitraryMap)
The latter is needed only by a limited set of routes, such as administrative processing. Handing that low-level freedom out to every API results in a design that depends on every single caller using it correctly, every time.
18. Summary
The Spring 2024 (Reiwa 6) PM Question 1 is a question that reads apart the topics of API security one at a time.
Using a JWT does not mean authentication is secure. Letting the attacker choose the signature algorithm lets the user ID be rewritten.
A valid JWT signature does not mean authorization is correct. Trusting the request’s mid lets a legitimate user access someone else’s information.
Being able to update your own object does not mean every property may be changed. Auto-binding internal state such as status lets permissions or billing status be rewritten.
Having an expiry on the authentication code does not mean it resists brute-forcing. You need to calculate the candidate space and attempt speed, and limit the number of failures.
Putting a rule into the WAF does not mean the vulnerability is fixed. Detection and blocking only buy time; you confirm the impact and ultimately update the library.
flowchart TB
accTitle: Mapping vulnerabilities to countermeasures
accDescr: Maps each vulnerability to its trust boundary and its countermeasure
A[JWT tampering] -->|Token verification| B[Fix the allowed algorithm]
C[mid swap] -->|Object-level authorization| D[Match against JWT subject/no mid needed]
E[status=paid] -->|Property-level authorization| F[Make the update DTO an allowlist]
G[4-digit code brute force] -->|Authentication attempt control| H[Failure limit/delay]
I[Log4Shell-type vulnerability] -->|Input to execution| J[Library update/WAF]
Figure 15: Mapping vulnerabilities to countermeasures. Separate the fix by which boundary was broken.
One principle runs through this entire question.
Never let success at the previous check become a reason to skip the next trust boundary.
Earlier articles in this series cover the stored XSS vulnerability in the Autumn 2023 (Reiwa 5) PM Question 1 and the guest Wi-Fi data exfiltration in the Autumn 2023 (Reiwa 5) PM Question 2. For a view of what to check across a website as a whole, see also Using IPA’s “How to Secure Your Website” as a Checklist.
flowchart TB
accTitle: Final summary
accDescr: Shows that success at the previous check is never a reason to skip the next trust boundary
A[Authentication succeeds] --> B[JWT signature verification]
B --> C[Object-level authorization]
C --> D[Property-level authorization]
D --> E[Attempt-rate limit]
E --> F[Input-to-execution boundary]
F --> G[WAF/library update]
Figure 16: Final summary. Trust boundaries are checked in stages, and none may be skipped.
References
-
IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Question Booklet. The question text this article is based on. ↩
-
IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Model Answers. The official model answer for each question. ↩
-
IPA, Spring 2024 (Reiwa 6) Registered Information Security Specialist Examination, PM Grading Commentary. An explanation of correct-answer rates and common mistakes. ↩
-
NIST, SP 800-63B: Authentication and Authenticator Management. Sets out digit counts for short-term secrets, attempt-rate limits, the failure count on reissue, and not using email for out-of-band authentication, among other requirements. ↩ ↩2
-
RFC Editor, RFC 7519: JSON Web Token (JWT). The specification for JWTs, including the Unsecured JWT and
alg=none. ↩ -
RFC Editor, RFC 8725: JSON Web Token Best Current Practices. The BCP that sets out fixing the set of allowed algorithms, and verifying the issuer, subject, and audience, among other practices. ↩
-
OWASP, API1:2023 Broken Object Level Authorization. Explains the need to check authorization for every object ID the user specifies. ↩
-
OWASP, API3:2023 Broken Object Property Level Authorization. Explains property-level authorization flaws, including Mass Assignment, and their countermeasures. ↩
-
Apache Logging Services, Security. Explains the impact of CVE-2021-44228, code execution via JNDI and LDAP, and the fixed versions. ↩
Related Articles
Recent articles sharing the same tags. Deepen your understanding with closely related topics.
Registered Information Security Specialist Exam, Autumn 2023 (Reiwa 5) Afternoon Q1 Commentary — The Stored XSS Where 16 Reviews Show Up as Only 2
Using Question 1 from the afternoon session of the Autumn 2023 (Reiwa 5) Registered Information Security Specialist Examination as the ca...
Registered Information Security Specialist Exam — Autumn 2023 (Reiwa 5) Afternoon Question 2 Explained — Files Walking Out Over the Guest Wi-Fi
Using Question 2 of the afternoon session of the Autumn 2023 (Reiwa 5) Registered Information Security Specialist exam, this article expl...
What Website Clients Should Know Too — Using IPA's 'How to Secure Your Website' as a Checklist
What standard should you use to check your company website's security against? This article explains the 11 vulnerabilities and counterme...
Information Security 10 Major Threats 2026 — How to Read the Ranking, and What SMEs Should Actually Guard Against
In IPA's 'Information Security 10 Major Threats 2026,' ransomware attacks took first place for the 11th year running, supply chain attack...
Where Should SMEs Start on Security? — A Walkthrough of IPA's 'Information Security Guidelines for SMEs,' 4th Edition
Where should small and medium-sized businesses start on security? Drawing on IPA's 'Information Security Guidelines for Small and Medium ...
Related Topics
These topic pages place the article in a broader service and decision context.
Windows Technical Topics
Topic hub for KomuraSoft LLC's Windows development, investigation, and legacy-asset articles.
Where This Topic Connects
This article connects naturally to the following service pages.
Website Development
Because in member APIs and smartphone integrations, JWT verification, object-level authorization, and restricting which properties can be updated map directly onto the security of the web system itself.
Technical Consulting & Design Review
Because identifying authorization gaps in existing APIs, assessing the blast radius of dependent libraries, and working out interim WAF rules through a design review all fall within the scope of technical consulting.
Frequently Asked Questions
Common questions about the topic of this article.
- Why can an attacker impersonate someone else even though JWT signature verification succeeded?
- In this question, the JWT management library accepted the JWT header's alg exactly as the attacker specified it, treating an alg=none JWT as valid with no signature at all. So even rewriting the payload's user ID still passes verification. The exam's answer is to verify the JWT header's alg and confirm that its value is not NONE. In practice, though, rejecting only NONE is not enough. Fix the algorithms allowed for use — for example, to RS256 — in server-side configuration, so the algorithm the token declares is never used directly to make the choice. You should also verify the issuer, audience, expiry, subject, and so on, as appropriate to the use case.
- Is comparing the user ID inside the JWT against the request's mid enough as an authorization countermeasure?
- For the purposes of this exam's answer, it is enough. Verifying, inside common module P, that the user ID contained in the JWT matches mid stops an attack that specifies someone else's mid. However, for an API that only handles the caller's own information, it is safer in practice not to accept mid from the client at all, and instead determine the user ID from the verified JWT's subject. Using something like GET /users/me or PUT /users/me makes it harder to end up with the comparison logic simply missing. An API where an administrator operates on another user should be split into a separate endpoint with its own authorization policy.
- Why can't ordinary input validation alone stop the attack that adds status?
- Because even if you validate the length of name or the range of age, that does nothing if status — a field that should never have been accepted in the first place — is auto-bound and passed straight to the internal object. The issue is not the format of a value; it's property-level authorization, i.e. whether the user is allowed to change that property at all. Define only name and age in the update input type, and reject unknown properties. Billing status must be changed only from server-trusted events, such as a successful result from the payment service.
- The four-digit authentication code expires after 10 minutes — why is it still dangerous?
- Because there are only 10,000 candidates from 0000 to 9999, and at 10 attempts per second an attacker succeeds after an average of 5,000 attempts — 500 seconds. The 10-minute validity period is 600 seconds, so trying non-repeating candidates in order lets an attacker check 6,000 of them within that window. Expiry time alone cannot stop brute-forcing. The candidate space, attempt speed, and attempt-count cap all need to be designed together.
- The exam's countermeasure is account locking — is immediate locking on its own enough in practice too?
- No. The blank in the question calls for logic that locks the account once the number of consecutive failures exceeds a threshold, but a fixed, permanent lock on its own lets an attacker deliberately lock someone else's account as a denial-of-service. In practice, combine a per-account failure count with graduated wait times, risk assessment of the source and device, notifications, and a recovery procedure. It also matters that issuing a new code does not reset the failure count back to zero.
- What is the point of setting the WAF to detect rather than block?
- It means legitimate business traffic doesn't get stopped even when a normal string is mistakenly judged an attack. In the model answer, the benefit is that it can prevent blocking caused by a false positive, and what should be done is to examine whether it's an attack whenever an alert is received. Detect mode is not a setting you leave alone. It is used as an observation period during which you check logs, filter out false positives, tune the rule, and then move to blocking. In an emergency where a known critical vulnerability is actually being exploited, it can be reasonable to weigh that against availability risk and choose to block from the start instead.
- Is library H in this question Log4j?
- The question withholds the product name, but the attack sequence — JNDI Lookup, an LDAP server, class retrieval from an HTTP server, a string embedded in an HTTP header, and a high CVSS v3.1 base score — reads naturally as an abstraction of CVE-2021-44228, known as Log4Shell. This article explains that correspondence, but the exam does not require you to name the specific product. It can be answered purely from the given attack procedure and the WAF specification.
- What should you take back into practice from this question?
- That succeeding at authentication, the JWT not having been tampered with, being allowed to access the target object, and being allowed to change the target property are all separate checks. On top of that, a short authentication code needs an attempt-rate limit, and for a critical library vulnerability you run impact confirmation, interim defence, and the root fix in parallel. The core practical takeaways are: consolidate authorization into shared components, make the input schema an allowlist, fix the JWT's verification conditions on the server side, and keep track of dependent libraries so you can update them.