Skip to content

Fix the reported security issue on v5 #2327

Description

@ae2079

we had this report from a user:
Hello Giveth security team,

I believe I found a service vulnerability in the Giveth frontend/backend project update flow. I did not execute this against production data. The evidence below comes from source review plus read-only public GraphQL queries.

Reporter: davethediver
Email: dave.the.diver1010@gmail.com
Ethereum address for reward distribution: 0xa882D8296CCd4eD5A4Ffe4cFaDd1bDf8DFA541D6

Summary

A project owner can store arbitrary HTML in a project update through addProjectUpdate / editProjectUpdate. If that update is selected as a featured project update, the homepage featured update card renders content with dangerouslySetInnerHTML without sanitization.

This creates a stored XSS path for visitors viewing the homepage/featured project update card. A particularly risky path is:

  1. A project owner creates a benign project update.
  2. Giveth admin features that project update via AdminJS.
  3. The project owner later edits the same featured update to include malicious HTML/event attributes.
  4. The homepage fetches the featured update and injects content directly into the DOM.

Impact

JavaScript execution in the giveth.io origin could read same-origin localStorage values, including the app's auth token storage keys, and perform authenticated GraphQL/API actions as the victim. It could also alter donation/project UI, show wallet-phishing prompts, or redirect users from the trusted Giveth origin.

Evidence

Frontend repo: Giveth/giveth-dapps-v2, local HEAD 4cb06d1

The homepage featured update card directly injects raw update HTML:

src/components/views/homepage/projectUpdatesBlock/ProjectUpdateSlide.tsx

<UpdateDesc
  dangerouslySetInnerHTML={{
    __html: update?.content || '',
  }}
/>

That content comes from the public featuredProjectUpdate(projectId) query:

src/apollo/gql/gqlProjects.ts

query featuredProjectUpdate($projectId: Int!) {
  featuredProjectUpdate(projectId: $projectId) {
    id
    title
    projectId
    userId
    content
    isMain
    createdAt
  }
}

Backend repo: Giveth/impact-graph, local HEAD 870dcbc

addProjectUpdate stores the caller-supplied content directly:

src/resolvers/projectResolver.ts

const update = ProjectUpdate.create({
  userId: user.userId,
  projectId: project.id,
  content,
  title,
  createdAt: new Date(),
  isMain: false,
});

const save = await ProjectUpdate.save(update);

editProjectUpdate lets the project owner replace content directly:

update.title = title;
update.content = content;
await update.save();

The entity stores content as a plain string column. The insert/update hook only generates a text summary; it does not sanitize the stored HTML:

src/entities/project.ts

@Field(_type => String)
@Column()
content: string;

@BeforeInsert()
setProjectUpdateContentSummary() {
  this.contentSummary = getHtmlTextSummary(this.content);
}

AdminJS selects existing update rows as featured records by projectUpdateId:

src/server/adminJs/tabs/projectsTab.ts

const featuredProject = FeaturedUpdate.create({
  projectUpdateId: update.id,
  projectId: update.project!.id,
});

await featuredProject.save();

The backend query returns the referenced update row:

src/resolvers/projectResolver.ts

const featuredProject = await FeaturedUpdate.createQueryBuilder(
  'featuredProject',
)
  .innerJoinAndSelect('featuredProject.projectUpdate', 'projectUpdate')
  .where('featuredProject.projectId = :projectId', { projectId })
  .getOne();

return featuredProject!.projectUpdate;

The backend tests also show that arbitrary HTML is preserved in editProjectUpdate responses rather than normalized/sanitized:

src/resolvers/projectResolver.test.ts

content: '<div>TestProjectUpdateAfterUpdateFateme</div>',
...
assert.equal(
  result.data.data.editProjectUpdate.content,
  '<div>TestProjectUpdateAfterUpdateFateme</div>',
);

I also confirmed with a read-only public GraphQL query to https://mainnet.serve.giveth.io/graphql that featuredProjectUpdate.content is returned as raw HTML for currently featured projects.

Safe reproduction outline

Please run this only in a local/staging environment:

  1. Create or seed a project owned by a normal user.
  2. Add a normal update as that project owner.
  3. Feature that update via AdminJS so a FeaturedUpdate row references it.
  4. As the project owner, call:
mutation EditProjectUpdate($updateId: Float!, $title: String!, $content: String!) {
  editProjectUpdate(updateId: $updateId, title: $title, content: $content) {
    id
    title
    content
  }
}

With variables similar to:

{
  "updateId": 123,
  "title": "Update",
  "content": "<p>hello</p><img src=x onerror=\"document.body.setAttribute('data-giveth-xss','1')\">"
}
  1. Visit the homepage/featured projects section where ProjectUpdateSlide renders that featured update.
  2. Observe that the DOM mutation from the event handler occurs. A real attacker could replace that harmless marker with token theft, UI manipulation, or same-origin API calls.

Suggested fixes

Sanitize project update HTML on the backend with a strict allowlist before storing or returning it. The allowlist should permit only the Quill tags/attributes Giveth intentionally supports, and should block event handler attributes, javascript: URLs, dangerous SVG/MathML, and unsafe inline style.

Also consider replacing the dangerouslySetInnerHTML use in ProjectUpdateSlide with the same safe rendering path used elsewhere, or render a text summary on the homepage card instead of raw rich HTML.

Because already-featured updates can be edited after admin selection, please also consider revalidating/sanitizing existing project_update.content rows and either snapshotting reviewed featured content or requiring re-review after a featured update is edited.

Thank you

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions