The issues described in this 7-year-old thread are still present in Unity today: Image.material behaves like renderer.sharedMaterial, forcing changes to every instance of an image whenever one Image instance’s material is edited. Using Image.GetComponent().GetMaterial() and Image.GetComponent().SetMaterial() produces the same issue.
In order to get around this issue, I had to resort to code like this:
material = new Material( image.material );
//code making changes to the newly instanced material
image.GetComponent<CanvasRenderer>().SetMaterial( material , 0 );
This took a while to puzzle out. For the sake of a better user experience, I suggest surfacing a separate shared material as a sharedMaterial property and making material an instanced material the user can edit directly, as in the Renderer component–or, in the alternative, making GetMaterial() in CanvasRenderer return an instanced material.